mirror of
https://github.com/home-assistant/core.git
synced 2025-09-23 11:59:37 +00:00
Compare commits
362 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e0367dc721 | ||
![]() |
35dc5ba742 | ||
![]() |
f7325a7d35 | ||
![]() |
a798b508bc | ||
![]() |
255d706c24 | ||
![]() |
cc8e0ef942 | ||
![]() |
d9a3b04e30 | ||
![]() |
4a65bed0eb | ||
![]() |
02adcc532f | ||
![]() |
fbd5ccf156 | ||
![]() |
8d69a4968f | ||
![]() |
e3b90ea3f7 | ||
![]() |
6c7355785a | ||
![]() |
a1ac1fb091 | ||
![]() |
8ca5a04a5d | ||
![]() |
04f75d6557 | ||
![]() |
26f09bae28 | ||
![]() |
2af961513d | ||
![]() |
fd1edf1bb6 | ||
![]() |
152a80abed | ||
![]() |
c289f9f099 | ||
![]() |
ed89d4869d | ||
![]() |
5b4187a9eb | ||
![]() |
5b79c1f9ef | ||
![]() |
b61b0321d6 | ||
![]() |
bb17cbdd83 | ||
![]() |
91e0395c1c | ||
![]() |
a71e0a4b29 | ||
![]() |
4e77969f5e | ||
![]() |
cf6480cda0 | ||
![]() |
1b3e5460a9 | ||
![]() |
a004e6aa68 | ||
![]() |
6610bbe7bb | ||
![]() |
2c7876fa66 | ||
![]() |
835f433cf7 | ||
![]() |
9d16edc1dc | ||
![]() |
e6d814da1d | ||
![]() |
7c5e852303 | ||
![]() |
149a3165e6 | ||
![]() |
82058f0b50 | ||
![]() |
63baf6fb0f | ||
![]() |
ad6315be5c | ||
![]() |
b4489e132c | ||
![]() |
2538cda9d4 | ||
![]() |
49a153a2e5 | ||
![]() |
d68148417f | ||
![]() |
8b21b415c4 | ||
![]() |
c28493098a | ||
![]() |
747490ab34 | ||
![]() |
ee816ed3dd | ||
![]() |
16f1ef5a44 | ||
![]() |
a2e705880d | ||
![]() |
e39997ca10 | ||
![]() |
620271c782 | ||
![]() |
d974cd4606 | ||
![]() |
247df5874b | ||
![]() |
e1060f154e | ||
![]() |
3f9e3d0905 | ||
![]() |
becc011135 | ||
![]() |
6660cf701d | ||
![]() |
280f49540e | ||
![]() |
a2e2c35011 | ||
![]() |
3a83f4bdbe | ||
![]() |
02d94f2fd0 | ||
![]() |
53a91ece4e | ||
![]() |
5446641f09 | ||
![]() |
e19c97af7c | ||
![]() |
7e696f19d3 | ||
![]() |
6906a19c01 | ||
![]() |
93f9f1b320 | ||
![]() |
3f427602ba | ||
![]() |
e25f216fd6 | ||
![]() |
b47be05efc | ||
![]() |
9d40ae96b5 | ||
![]() |
f131959f4b | ||
![]() |
0054904454 | ||
![]() |
5c8d8a290f | ||
![]() |
dcb3a57476 | ||
![]() |
78225c9ddd | ||
![]() |
215215747e | ||
![]() |
5cd85f9f00 | ||
![]() |
4674cb406e | ||
![]() |
82b77a8108 | ||
![]() |
1f9721bad3 | ||
![]() |
6aba87f3a6 | ||
![]() |
edad387b12 | ||
![]() |
f4b8a95205 | ||
![]() |
29adc6a27b | ||
![]() |
fed6625324 | ||
![]() |
1de97e3a35 | ||
![]() |
a6536bb622 | ||
![]() |
59e43ab6e4 | ||
![]() |
29f128eaad | ||
![]() |
d88efe405e | ||
![]() |
f866ff5f2b | ||
![]() |
efa0e1b2ac | ||
![]() |
4a432781ca | ||
![]() |
6c2dc521a3 | ||
![]() |
b112a742b2 | ||
![]() |
46cb9cff41 | ||
![]() |
9f65dcf4ba | ||
![]() |
13c0a59e28 | ||
![]() |
7d69b90eac | ||
![]() |
60dd94d5b0 | ||
![]() |
2196bd66c7 | ||
![]() |
8895f9b70a | ||
![]() |
a074cf4afd | ||
![]() |
2fd6431cff | ||
![]() |
5642027ffb | ||
![]() |
93272e3083 | ||
![]() |
2b5e7c2611 | ||
![]() |
683d960fa5 | ||
![]() |
d445c16697 | ||
![]() |
e4df0481da | ||
![]() |
3d1a5f76b2 | ||
![]() |
263bdaa565 | ||
![]() |
212660330f | ||
![]() |
f83d4e524b | ||
![]() |
6bffd9a892 | ||
![]() |
02e03340df | ||
![]() |
e92e26b73a | ||
![]() |
f69fc79fd1 | ||
![]() |
e7d982ee11 | ||
![]() |
94132e5572 | ||
![]() |
7920c5e5e8 | ||
![]() |
df59f87f34 | ||
![]() |
c0a2e6b655 | ||
![]() |
b4b2302e04 | ||
![]() |
098d82f4d3 | ||
![]() |
2883aacfa3 | ||
![]() |
ec440dface | ||
![]() |
3d385796da | ||
![]() |
3e14b2dc61 | ||
![]() |
7547a92479 | ||
![]() |
625c2aa19d | ||
![]() |
ec69edcb79 | ||
![]() |
f823afeadc | ||
![]() |
903db07feb | ||
![]() |
43cee39528 | ||
![]() |
d5cc3208af | ||
![]() |
94c8d74a66 | ||
![]() |
b3c851502d | ||
![]() |
a74e35795c | ||
![]() |
b464096edb | ||
![]() |
5228282f69 | ||
![]() |
fd67a079db | ||
![]() |
3ae4eba457 | ||
![]() |
dba326f16b | ||
![]() |
2b06fbbcf0 | ||
![]() |
d278dd9477 | ||
![]() |
fe03c9da68 | ||
![]() |
a6a6a7b69c | ||
![]() |
29df13abe9 | ||
![]() |
6db5ff98ed | ||
![]() |
25607c7129 | ||
![]() |
58f1d1754e | ||
![]() |
6273ad85f8 | ||
![]() |
a68af0a3a9 | ||
![]() |
f8de0594b9 | ||
![]() |
8541ae0360 | ||
![]() |
dfc345a921 | ||
![]() |
87f236c05c | ||
![]() |
0127974f09 | ||
![]() |
108082fd07 | ||
![]() |
61d6bd0cb5 | ||
![]() |
3a2138131c | ||
![]() |
ec65ebacd3 | ||
![]() |
b45e49902c | ||
![]() |
12781bf842 | ||
![]() |
d0ca8e62a4 | ||
![]() |
c37cd835b9 | ||
![]() |
3ee3ae7633 | ||
![]() |
02f174e2e6 | ||
![]() |
16cf16e418 | ||
![]() |
de12c21ce7 | ||
![]() |
b8c9f67533 | ||
![]() |
ba73ac12ba | ||
![]() |
0a219081ea | ||
![]() |
b0163b65c6 | ||
![]() |
83e3f680bf | ||
![]() |
e443dc1274 | ||
![]() |
717a21dc7b | ||
![]() |
1b6f0b78aa | ||
![]() |
e8d4a25635 | ||
![]() |
162e502962 | ||
![]() |
3cc94f7d6a | ||
![]() |
dfac9c5e03 | ||
![]() |
c96458c7e4 | ||
![]() |
9a867cbb75 | ||
![]() |
b15caf31a9 | ||
![]() |
0331ebdd47 | ||
![]() |
0b7d2aa4d7 | ||
![]() |
821de0e369 | ||
![]() |
02bcdf5162 | ||
![]() |
e7e2f4e786 | ||
![]() |
e969d364e6 | ||
![]() |
4862f6d516 | ||
![]() |
36ed3b1177 | ||
![]() |
9cc20fc6b8 | ||
![]() |
dd239661e7 | ||
![]() |
0bbb56dd05 | ||
![]() |
0bfcb99c04 | ||
![]() |
bdd255176c | ||
![]() |
e5f08ee657 | ||
![]() |
12c649ba27 | ||
![]() |
5eb7f5fcf3 | ||
![]() |
48b6f41048 | ||
![]() |
16d045aa9f | ||
![]() |
66ad69bff3 | ||
![]() |
599288dae2 | ||
![]() |
2594f11b34 | ||
![]() |
ad7edb4abe | ||
![]() |
9e73853d65 | ||
![]() |
504cb26329 | ||
![]() |
20428e670b | ||
![]() |
78af0a4705 | ||
![]() |
65e9e4a2f3 | ||
![]() |
922b332766 | ||
![]() |
1ffa8fcbba | ||
![]() |
785786ecd1 | ||
![]() |
b78ad7c2b8 | ||
![]() |
4412ce3b86 | ||
![]() |
238430136e | ||
![]() |
15113ae854 | ||
![]() |
7ce6ae9421 | ||
![]() |
f9bc0c9dab | ||
![]() |
bf95658e24 | ||
![]() |
3c57475c8f | ||
![]() |
e8b16f0dfd | ||
![]() |
c1cf3679aa | ||
![]() |
871afd2bf2 | ||
![]() |
5595ef0783 | ||
![]() |
5a3e0c6b25 | ||
![]() |
a8e9ccbf1a | ||
![]() |
44708ed8bb | ||
![]() |
8e44d797a3 | ||
![]() |
419a92db43 | ||
![]() |
986853d497 | ||
![]() |
d4d233536f | ||
![]() |
21acdbbbfd | ||
![]() |
394966e979 | ||
![]() |
aac7cf129d | ||
![]() |
0146f35687 | ||
![]() |
e2c9af42e8 | ||
![]() |
c9c8e8160f | ||
![]() |
c5972690ac | ||
![]() |
c2a261370e | ||
![]() |
4c139076ef | ||
![]() |
4bccb6e72b | ||
![]() |
f05288b597 | ||
![]() |
2d5faaf3f8 | ||
![]() |
dd6d18102f | ||
![]() |
14d715e7fc | ||
![]() |
b820b7c47d | ||
![]() |
afe4647896 | ||
![]() |
4df186787a | ||
![]() |
b353f45d84 | ||
![]() |
fc2c195ed4 | ||
![]() |
f1d5f95f7c | ||
![]() |
59f935beb0 | ||
![]() |
ddb85cee7b | ||
![]() |
14bff5a375 | ||
![]() |
9311b02369 | ||
![]() |
82090f5060 | ||
![]() |
186a299215 | ||
![]() |
00068e88b0 | ||
![]() |
2c1a76cf92 | ||
![]() |
c65e72886c | ||
![]() |
e13f206a06 | ||
![]() |
8d405c4585 | ||
![]() |
2ac4d30736 | ||
![]() |
fa17e6d5ab | ||
![]() |
212b9df87d | ||
![]() |
44552937b6 | ||
![]() |
8f3c84b349 | ||
![]() |
29b8f76e57 | ||
![]() |
29838ce1ed | ||
![]() |
5cf753422b | ||
![]() |
ded5329f03 | ||
![]() |
e3fc59ff3d | ||
![]() |
6166a7191b | ||
![]() |
1169ac568b | ||
![]() |
1ab1503641 | ||
![]() |
2abd3844cf | ||
![]() |
854bdfb6f2 | ||
![]() |
5f8dcd45c1 | ||
![]() |
38bb8ef4d2 | ||
![]() |
7cc3102209 | ||
![]() |
525e220395 | ||
![]() |
140fd5adaf | ||
![]() |
5b2bf8fd17 | ||
![]() |
5e2de4531f | ||
![]() |
c33edbe5bb | ||
![]() |
85ba29012f | ||
![]() |
5975ec340b | ||
![]() |
3adfb86a19 | ||
![]() |
b3cbce3566 | ||
![]() |
7a2820ded9 | ||
![]() |
af4fb03230 | ||
![]() |
a5da21a426 | ||
![]() |
16e36dca97 | ||
![]() |
f522c6c8c7 | ||
![]() |
1bdbe90d2a | ||
![]() |
8ed1b1782e | ||
![]() |
31dd06bd12 | ||
![]() |
de7bbd3e24 | ||
![]() |
fbbc681ad4 | ||
![]() |
9e7f516d07 | ||
![]() |
066784c88f | ||
![]() |
81355a0e23 | ||
![]() |
215c7e0e14 | ||
![]() |
233284056a | ||
![]() |
56c69d9a25 | ||
![]() |
35cd6b9abf | ||
![]() |
823f27db5a | ||
![]() |
ad5101c5c0 | ||
![]() |
3bf389639b | ||
![]() |
31973de2d5 | ||
![]() |
524b48be7d | ||
![]() |
0c5ca3084e | ||
![]() |
49747684a0 | ||
![]() |
3495932eb0 | ||
![]() |
d73a4e1ed5 | ||
![]() |
14f5cab71d | ||
![]() |
29ab1935cb | ||
![]() |
bdc098645b | ||
![]() |
492874c4a0 | ||
![]() |
5e65d8d3c3 | ||
![]() |
1c329ff708 | ||
![]() |
e807274d7e | ||
![]() |
4950cbee1c | ||
![]() |
0bf64e9a2c | ||
![]() |
3076fc5f25 | ||
![]() |
b51d81edba | ||
![]() |
6faf9e8bbe | ||
![]() |
c0307dca3a | ||
![]() |
5b94807923 | ||
![]() |
4170eb0f83 | ||
![]() |
874c8fea03 | ||
![]() |
a4204b440c | ||
![]() |
f8e48a9230 | ||
![]() |
04231bcb54 | ||
![]() |
f170c80bea | ||
![]() |
394f16987d | ||
![]() |
f06c0a8b54 | ||
![]() |
36b157b85a | ||
![]() |
7a3c2e1f6c | ||
![]() |
fae80621fb | ||
![]() |
1edbdcb67b | ||
![]() |
99318b7b11 | ||
![]() |
a6107198b9 | ||
![]() |
203217c175 | ||
![]() |
c316735996 | ||
![]() |
40829d9d76 | ||
![]() |
68b077ffaa | ||
![]() |
8528b4db3a | ||
![]() |
48d1bc7c13 | ||
![]() |
08e85696c1 | ||
![]() |
e08ba6703c | ||
![]() |
9ef14efa29 | ||
![]() |
fc395de511 | ||
![]() |
751e2f4125 |
51
.coveragerc
51
.coveragerc
@@ -46,7 +46,6 @@ omit =
|
||||
homeassistant/components/android_ip_webcam/*
|
||||
homeassistant/components/anel_pwrctrl/switch.py
|
||||
homeassistant/components/anthemav/media_player.py
|
||||
homeassistant/components/apache_kafka/*
|
||||
homeassistant/components/apcupsd/*
|
||||
homeassistant/components/apple_tv/*
|
||||
homeassistant/components/aqualogic/*
|
||||
@@ -68,8 +67,8 @@ omit =
|
||||
homeassistant/components/aurora_abb_powerone/sensor.py
|
||||
homeassistant/components/avea/light.py
|
||||
homeassistant/components/avion/light.py
|
||||
homeassistant/components/avri/const.py
|
||||
homeassistant/components/avri/sensor.py
|
||||
homeassistant/components/azure_event_hub/*
|
||||
homeassistant/components/azure_service_bus/*
|
||||
homeassistant/components/baidu/tts.py
|
||||
homeassistant/components/beewi_smartclim/sensor.py
|
||||
@@ -79,7 +78,12 @@ omit =
|
||||
homeassistant/components/bh1750/sensor.py
|
||||
homeassistant/components/bitcoin/sensor.py
|
||||
homeassistant/components/bizkaibus/sensor.py
|
||||
homeassistant/components/blink/*
|
||||
homeassistant/components/blink/__init__.py
|
||||
homeassistant/components/blink/alarm_control_panel.py
|
||||
homeassistant/components/blink/binary_sensor.py
|
||||
homeassistant/components/blink/camera.py
|
||||
homeassistant/components/blink/const.py
|
||||
homeassistant/components/blink/sensor.py
|
||||
homeassistant/components/blinksticklight/light.py
|
||||
homeassistant/components/blinkt/light.py
|
||||
homeassistant/components/blockchain/sensor.py
|
||||
@@ -154,9 +158,14 @@ omit =
|
||||
homeassistant/components/deluge/switch.py
|
||||
homeassistant/components/denon/media_player.py
|
||||
homeassistant/components/denonavr/media_player.py
|
||||
homeassistant/components/denonavr/receiver.py
|
||||
homeassistant/components/deutsche_bahn/sensor.py
|
||||
homeassistant/components/devolo_home_control/__init__.py
|
||||
homeassistant/components/devolo_home_control/binary_sensor.py
|
||||
homeassistant/components/devolo_home_control/const.py
|
||||
homeassistant/components/devolo_home_control/devolo_device.py
|
||||
homeassistant/components/devolo_home_control/sensor.py
|
||||
homeassistant/components/devolo_home_control/subscriber.py
|
||||
homeassistant/components/devolo_home_control/switch.py
|
||||
homeassistant/components/dht/sensor.py
|
||||
homeassistant/components/digital_ocean/*
|
||||
@@ -255,7 +264,6 @@ omit =
|
||||
homeassistant/components/folder_watcher/*
|
||||
homeassistant/components/foobot/sensor.py
|
||||
homeassistant/components/fortios/device_tracker.py
|
||||
homeassistant/components/fortigate/*
|
||||
homeassistant/components/foscam/camera.py
|
||||
homeassistant/components/foscam/const.py
|
||||
homeassistant/components/foursquare/*
|
||||
@@ -284,6 +292,7 @@ omit =
|
||||
homeassistant/components/gitlab_ci/sensor.py
|
||||
homeassistant/components/gitter/sensor.py
|
||||
homeassistant/components/glances/__init__.py
|
||||
homeassistant/components/glances/const.py
|
||||
homeassistant/components/glances/sensor.py
|
||||
homeassistant/components/gntp/notify.py
|
||||
homeassistant/components/goalfeed/*
|
||||
@@ -339,6 +348,8 @@ omit =
|
||||
homeassistant/components/hunterdouglas_powerview/sensor.py
|
||||
homeassistant/components/hunterdouglas_powerview/cover.py
|
||||
homeassistant/components/hunterdouglas_powerview/entity.py
|
||||
homeassistant/components/hvv_departures/sensor.py
|
||||
homeassistant/components/hvv_departures/__init__.py
|
||||
homeassistant/components/hydrawise/*
|
||||
homeassistant/components/hyperion/light.py
|
||||
homeassistant/components/ialarm/alarm_control_panel.py
|
||||
@@ -431,7 +442,6 @@ omit =
|
||||
homeassistant/components/linux_battery/sensor.py
|
||||
homeassistant/components/lirc/*
|
||||
homeassistant/components/llamalab_automate/notify.py
|
||||
homeassistant/components/lockitron/lock.py
|
||||
homeassistant/components/logi_circle/__init__.py
|
||||
homeassistant/components/logi_circle/camera.py
|
||||
homeassistant/components/logi_circle/const.py
|
||||
@@ -538,6 +548,7 @@ omit =
|
||||
homeassistant/components/notion/sensor.py
|
||||
homeassistant/components/noaa_tides/sensor.py
|
||||
homeassistant/components/norway_air/air_quality.py
|
||||
homeassistant/components/notify_events/notify.py
|
||||
homeassistant/components/nsw_fuel_station/sensor.py
|
||||
homeassistant/components/nuimo_controller/*
|
||||
homeassistant/components/nuki/lock.py
|
||||
@@ -714,7 +725,11 @@ omit =
|
||||
homeassistant/components/sinch/*
|
||||
homeassistant/components/slide/*
|
||||
homeassistant/components/sma/sensor.py
|
||||
homeassistant/components/smappee/*
|
||||
homeassistant/components/smappee/__init__.py
|
||||
homeassistant/components/smappee/api.py
|
||||
homeassistant/components/smappee/binary_sensor.py
|
||||
homeassistant/components/smappee/sensor.py
|
||||
homeassistant/components/smappee/switch.py
|
||||
homeassistant/components/smarty/*
|
||||
homeassistant/components/smarthab/*
|
||||
homeassistant/components/sms/*
|
||||
@@ -740,7 +755,8 @@ omit =
|
||||
homeassistant/components/spotcrime/sensor.py
|
||||
homeassistant/components/spotify/__init__.py
|
||||
homeassistant/components/spotify/media_player.py
|
||||
homeassistant/components/squeezebox/*
|
||||
homeassistant/components/squeezebox/__init__.py
|
||||
homeassistant/components/squeezebox/media_player.py
|
||||
homeassistant/components/starline/*
|
||||
homeassistant/components/starlingbank/sensor.py
|
||||
homeassistant/components/steam_online/sensor.py
|
||||
@@ -797,6 +813,7 @@ omit =
|
||||
homeassistant/components/thomson/device_tracker.py
|
||||
homeassistant/components/tibber/*
|
||||
homeassistant/components/tikteck/light.py
|
||||
homeassistant/components/tile/__init__.py
|
||||
homeassistant/components/tile/device_tracker.py
|
||||
homeassistant/components/time_date/sensor.py
|
||||
homeassistant/components/tmb/sensor.py
|
||||
@@ -804,7 +821,16 @@ omit =
|
||||
homeassistant/components/todoist/const.py
|
||||
homeassistant/components/tof/sensor.py
|
||||
homeassistant/components/tomato/device_tracker.py
|
||||
homeassistant/components/toon/*
|
||||
homeassistant/components/toon/__init__.py
|
||||
homeassistant/components/toon/binary_sensor.py
|
||||
homeassistant/components/toon/climate.py
|
||||
homeassistant/components/toon/const.py
|
||||
homeassistant/components/toon/coordinator.py
|
||||
homeassistant/components/toon/helpers.py
|
||||
homeassistant/components/toon/models.py
|
||||
homeassistant/components/toon/oauth2.py
|
||||
homeassistant/components/toon/sensor.py
|
||||
homeassistant/components/toon/switch.py
|
||||
homeassistant/components/torque/sensor.py
|
||||
homeassistant/components/totalconnect/*
|
||||
homeassistant/components/touchline/climate.py
|
||||
@@ -891,7 +917,14 @@ omit =
|
||||
homeassistant/components/xeoma/camera.py
|
||||
homeassistant/components/xfinity/device_tracker.py
|
||||
homeassistant/components/xiaomi/camera.py
|
||||
homeassistant/components/xiaomi_aqara/*
|
||||
homeassistant/components/xiaomi_aqara/__init__.py
|
||||
homeassistant/components/xiaomi_aqara/binary_sensor.py
|
||||
homeassistant/components/xiaomi_aqara/const.py
|
||||
homeassistant/components/xiaomi_aqara/cover.py
|
||||
homeassistant/components/xiaomi_aqara/light.py
|
||||
homeassistant/components/xiaomi_aqara/lock.py
|
||||
homeassistant/components/xiaomi_aqara/sensor.py
|
||||
homeassistant/components/xiaomi_aqara/switch.py
|
||||
homeassistant/components/xiaomi_miio/__init__.py
|
||||
homeassistant/components/xiaomi_miio/air_quality.py
|
||||
homeassistant/components/xiaomi_miio/alarm_control_panel.py
|
||||
|
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -8,3 +8,5 @@
|
||||
*.png binary
|
||||
*.zip binary
|
||||
*.mp3 binary
|
||||
|
||||
Dockerfile.dev linguist-language=Dockerfile
|
||||
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -21,7 +21,7 @@
|
||||
|
||||
- Home Assistant Core release with the issue:
|
||||
- Last working Home Assistant Core release (if known):
|
||||
- Operating environment (Home Assistant/Supervised/Docker/venv):
|
||||
- Operating environment (OS/Container/Supervised/Core):
|
||||
- Integration causing this issue:
|
||||
- Link to integration documentation on our website:
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@@ -25,7 +25,7 @@ about: Report an issue with Home Assistant Core
|
||||
|
||||
- Home Assistant Core release with the issue:
|
||||
- Last working Home Assistant Core release (if known):
|
||||
- Operating environment (Home Assistant/Supervised/Docker/venv):
|
||||
- Operating environment (OS/Container/Supervised/Core):
|
||||
- Integration causing this issue:
|
||||
- Link to integration documentation on our website:
|
||||
|
||||
|
18
CODEOWNERS
18
CODEOWNERS
@@ -46,7 +46,7 @@ homeassistant/components/auth/* @home-assistant/core
|
||||
homeassistant/components/automation/* @home-assistant/core
|
||||
homeassistant/components/avea/* @pattyland
|
||||
homeassistant/components/avri/* @timvancann
|
||||
homeassistant/components/awair/* @danielsjf
|
||||
homeassistant/components/awair/* @ahayworth @danielsjf
|
||||
homeassistant/components/aws/* @awarecan @robbiet480
|
||||
homeassistant/components/axis/* @Kane610
|
||||
homeassistant/components/azure_event_hub/* @eavanvalkenburg
|
||||
@@ -57,7 +57,7 @@ homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
||||
homeassistant/components/blebox/* @gadgetmobile
|
||||
homeassistant/components/blink/* @fronzbot
|
||||
homeassistant/components/bmp280/* @belidzs
|
||||
homeassistant/components/bmw_connected_drive/* @gerard33
|
||||
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
|
||||
homeassistant/components/bom/* @maddenp
|
||||
homeassistant/components/braviatv/* @robbiet480 @bieniu
|
||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||
@@ -86,6 +86,7 @@ homeassistant/components/cpuspeed/* @fabaff
|
||||
homeassistant/components/cups/* @fabaff
|
||||
homeassistant/components/daikin/* @fredrike
|
||||
homeassistant/components/darksky/* @fabaff
|
||||
homeassistant/components/debugpy/* @frenck
|
||||
homeassistant/components/deconz/* @Kane610
|
||||
homeassistant/components/delijn/* @bollewolle @Emilv2
|
||||
homeassistant/components/demo/* @home-assistant/core
|
||||
@@ -133,7 +134,6 @@ homeassistant/components/flock/* @fabaff
|
||||
homeassistant/components/flume/* @ChrisMandich @bdraco
|
||||
homeassistant/components/flunearyou/* @bachya
|
||||
homeassistant/components/forked_daapd/* @uvjustin
|
||||
homeassistant/components/fortigate/* @kifeo
|
||||
homeassistant/components/fortios/* @kimfrellsen
|
||||
homeassistant/components/foscam/* @skgsergio
|
||||
homeassistant/components/foursquare/* @robbiet480
|
||||
@@ -184,7 +184,10 @@ homeassistant/components/http/* @home-assistant/core
|
||||
homeassistant/components/huawei_lte/* @scop @fphammerle
|
||||
homeassistant/components/huawei_router/* @abmantis
|
||||
homeassistant/components/hue/* @balloob
|
||||
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
|
||||
homeassistant/components/hunterdouglas_powerview/* @bdraco
|
||||
homeassistant/components/hvv_departures/* @vigonotion
|
||||
homeassistant/components/hydrawise/* @ptcryan
|
||||
homeassistant/components/iammeter/* @lewei50
|
||||
homeassistant/components/iaqualink/* @flz
|
||||
homeassistant/components/icloud/* @Quentame
|
||||
@@ -243,6 +246,7 @@ homeassistant/components/melissa/* @kennedyshead
|
||||
homeassistant/components/met/* @danielhiversen
|
||||
homeassistant/components/meteo_france/* @victorcerutti @oncleben31 @Quentame
|
||||
homeassistant/components/meteoalarm/* @rolfberkenbosch
|
||||
homeassistant/components/metoffice/* @MrHarcombe
|
||||
homeassistant/components/miflora/* @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/mikrotik/* @engrbm87
|
||||
homeassistant/components/mill/* @danielhiversen
|
||||
@@ -274,6 +278,7 @@ homeassistant/components/nissan_leaf/* @filcole
|
||||
homeassistant/components/nmbs/* @thibmaek
|
||||
homeassistant/components/no_ip/* @fabaff
|
||||
homeassistant/components/notify/* @home-assistant/core
|
||||
homeassistant/components/notify_events/* @matrozov @papajojo
|
||||
homeassistant/components/notion/* @bachya
|
||||
homeassistant/components/nsw_fuel_station/* @nickw444
|
||||
homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
|
||||
@@ -311,9 +316,10 @@ homeassistant/components/plaato/* @JohNan
|
||||
homeassistant/components/plant/* @ChristianKuehnel
|
||||
homeassistant/components/plex/* @jjlawren
|
||||
homeassistant/components/plugwise/* @CoMPaTech @bouwew
|
||||
homeassistant/components/plum_lightpad/* @ColinHarrington
|
||||
homeassistant/components/plum_lightpad/* @ColinHarrington @prystupa
|
||||
homeassistant/components/point/* @fredrike
|
||||
homeassistant/components/powerwall/* @bdraco @jrester
|
||||
homeassistant/components/prometheus/* @knyar
|
||||
homeassistant/components/proxmoxve/* @k4ds3 @jhollowe
|
||||
homeassistant/components/ps4/* @ktnrg45
|
||||
homeassistant/components/ptvsd/* @swamp-ig
|
||||
@@ -362,6 +368,7 @@ homeassistant/components/sinch/* @bendikrb
|
||||
homeassistant/components/sisyphus/* @jkeljo
|
||||
homeassistant/components/slide/* @ualex73
|
||||
homeassistant/components/sma/* @kellerza
|
||||
homeassistant/components/smappee/* @bsmappee
|
||||
homeassistant/components/smarthab/* @outadoc
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
homeassistant/components/smarty/* @z0mbieprocess
|
||||
@@ -375,7 +382,7 @@ homeassistant/components/somfy/* @tetienne
|
||||
homeassistant/components/sonarr/* @ctalkington
|
||||
homeassistant/components/songpal/* @rytilahti @shenxn
|
||||
homeassistant/components/spaceapi/* @fabaff
|
||||
homeassistant/components/speedtestdotnet/* @rohankapoorcom
|
||||
homeassistant/components/speedtestdotnet/* @rohankapoorcom @engrbm87
|
||||
homeassistant/components/spider/* @peternijssen
|
||||
homeassistant/components/spotify/* @frenck
|
||||
homeassistant/components/sql/* @dgomes
|
||||
@@ -453,7 +460,6 @@ homeassistant/components/watson_tts/* @rutkai
|
||||
homeassistant/components/weather/* @fabaff
|
||||
homeassistant/components/webostv/* @bendavid
|
||||
homeassistant/components/websocket_api/* @home-assistant/core
|
||||
homeassistant/components/wemo/* @sqldiablo
|
||||
homeassistant/components/wiffi/* @mampfes
|
||||
homeassistant/components/withings/* @vangorra
|
||||
homeassistant/components/wled/* @frenck
|
||||
|
@@ -117,7 +117,8 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
|
||||
Mfa module should extend SetupFlow
|
||||
"""
|
||||
user = await self.hass.auth.async_get_user(user_id) # type: ignore
|
||||
user = await self.hass.auth.async_get_user(user_id)
|
||||
assert user is not None
|
||||
return TotpSetupFlow(self, self.input_schema, user)
|
||||
|
||||
async def async_setup_user(self, user_id: str, setup_data: Any) -> str:
|
||||
|
@@ -175,7 +175,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
"""Initialize the login flow."""
|
||||
self._auth_provider = auth_provider
|
||||
self._auth_module_id: Optional[str] = None
|
||||
self._auth_manager = auth_provider.hass.auth # type: ignore
|
||||
self._auth_manager = auth_provider.hass.auth
|
||||
self.available_mfa_modules: Dict[str, str] = {}
|
||||
self.created_at = dt_util.utcnow()
|
||||
self.invalid_mfa_times = 0
|
||||
@@ -224,6 +224,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
|
||||
errors = {}
|
||||
|
||||
assert self._auth_module_id is not None
|
||||
auth_module = self._auth_manager.get_auth_mfa_module(self._auth_module_id)
|
||||
if auth_module is None:
|
||||
# Given an invalid input to async_step_select_mfa_module
|
||||
@@ -234,7 +235,9 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
auth_module, "async_initialize_login_mfa_step"
|
||||
):
|
||||
try:
|
||||
await auth_module.async_initialize_login_mfa_step(self.user.id)
|
||||
await auth_module.async_initialize_login_mfa_step( # type: ignore
|
||||
self.user.id
|
||||
)
|
||||
except HomeAssistantError:
|
||||
_LOGGER.exception("Error initializing MFA step")
|
||||
return self.async_abort(reason="unknown_error")
|
||||
|
@@ -1,6 +1,7 @@
|
||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||
import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
@@ -20,7 +21,12 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import DATA_SETUP, DATA_SETUP_STARTED, async_setup_component
|
||||
from homeassistant.setup import (
|
||||
DATA_SETUP,
|
||||
DATA_SETUP_STARTED,
|
||||
async_set_domains_to_be_loaded,
|
||||
async_setup_component,
|
||||
)
|
||||
from homeassistant.util.logging import async_activate_log_queue_handler
|
||||
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||
from homeassistant.util.yaml import clear_secret_cache
|
||||
@@ -34,12 +40,18 @@ DATA_LOGGING = "logging"
|
||||
|
||||
LOG_SLOW_STARTUP_INTERVAL = 60
|
||||
|
||||
DEBUGGER_INTEGRATIONS = {"ptvsd"}
|
||||
DEBUGGER_INTEGRATIONS = {"debugpy", "ptvsd"}
|
||||
CORE_INTEGRATIONS = ("homeassistant", "persistent_notification")
|
||||
LOGGING_INTEGRATIONS = {"logger", "system_log", "sentry"}
|
||||
STAGE_1_INTEGRATIONS = {
|
||||
LOGGING_INTEGRATIONS = {
|
||||
# Set log levels
|
||||
"logger",
|
||||
# Error logging
|
||||
"system_log",
|
||||
"sentry",
|
||||
# To record data
|
||||
"recorder",
|
||||
}
|
||||
STAGE_1_INTEGRATIONS = {
|
||||
# To make sure we forward data to other instances
|
||||
"mqtt_eventstream",
|
||||
# To provide account link implementations
|
||||
@@ -50,7 +62,6 @@ STAGE_1_INTEGRATIONS = {
|
||||
# as possible so problem integrations can
|
||||
# be removed
|
||||
"frontend",
|
||||
"config",
|
||||
}
|
||||
|
||||
|
||||
@@ -125,8 +136,12 @@ async def async_setup_hass(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
safe_mode = True
|
||||
old_config = hass.config
|
||||
hass = core.HomeAssistant()
|
||||
hass.config.config_dir = config_dir
|
||||
hass.config.skip_pip = old_config.skip_pip
|
||||
hass.config.internal_url = old_config.internal_url
|
||||
hass.config.external_url = old_config.external_url
|
||||
hass.config.config_dir = old_config.config_dir
|
||||
|
||||
if safe_mode:
|
||||
_LOGGER.info("Starting in safe mode")
|
||||
@@ -327,76 +342,130 @@ def _get_domains(hass: core.HomeAssistant, config: Dict[str, Any]) -> Set[str]:
|
||||
return domains
|
||||
|
||||
|
||||
async def _async_log_pending_setups(
|
||||
domains: Set[str], setup_started: Dict[str, datetime]
|
||||
) -> None:
|
||||
"""Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL."""
|
||||
while True:
|
||||
await asyncio.sleep(LOG_SLOW_STARTUP_INTERVAL)
|
||||
remaining = [domain for domain in domains if domain in setup_started]
|
||||
|
||||
if remaining:
|
||||
_LOGGER.info(
|
||||
"Waiting on integrations to complete setup: %s", ", ".join(remaining),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_multi_components(
|
||||
hass: core.HomeAssistant,
|
||||
domains: Set[str],
|
||||
config: Dict[str, Any],
|
||||
setup_started: Dict[str, datetime],
|
||||
) -> None:
|
||||
"""Set up multiple domains. Log on failure."""
|
||||
futures = {
|
||||
domain: hass.async_create_task(async_setup_component(hass, domain, config))
|
||||
for domain in domains
|
||||
}
|
||||
log_task = asyncio.create_task(_async_log_pending_setups(domains, setup_started))
|
||||
await asyncio.wait(futures.values())
|
||||
log_task.cancel()
|
||||
errors = [domain for domain in domains if futures[domain].exception()]
|
||||
for domain in errors:
|
||||
exception = futures[domain].exception()
|
||||
assert exception is not None
|
||||
_LOGGER.error(
|
||||
"Error setting up integration %s - received exception",
|
||||
domain,
|
||||
exc_info=(type(exception), exception, exception.__traceback__),
|
||||
)
|
||||
|
||||
|
||||
async def _async_set_up_integrations(
|
||||
hass: core.HomeAssistant, config: Dict[str, Any]
|
||||
) -> None:
|
||||
"""Set up all the integrations."""
|
||||
|
||||
setup_started = hass.data[DATA_SETUP_STARTED] = {}
|
||||
domains_to_setup = _get_domains(hass, config)
|
||||
|
||||
async def async_setup_multi_components(domains: Set[str]) -> None:
|
||||
"""Set up multiple domains. Log on failure."""
|
||||
# Resolve all dependencies so we know all integrations
|
||||
# that will have to be loaded and start rightaway
|
||||
integration_cache: Dict[str, loader.Integration] = {}
|
||||
to_resolve = domains_to_setup
|
||||
while to_resolve:
|
||||
old_to_resolve = to_resolve
|
||||
to_resolve = set()
|
||||
|
||||
async def _async_log_pending_setups() -> None:
|
||||
"""Periodic log of setups that are pending for longer than LOG_SLOW_STARTUP_INTERVAL."""
|
||||
while True:
|
||||
await asyncio.sleep(LOG_SLOW_STARTUP_INTERVAL)
|
||||
remaining = [domain for domain in domains if domain in setup_started]
|
||||
|
||||
if remaining:
|
||||
_LOGGER.info(
|
||||
"Waiting on integrations to complete setup: %s",
|
||||
", ".join(remaining),
|
||||
)
|
||||
|
||||
futures = {
|
||||
domain: hass.async_create_task(async_setup_component(hass, domain, config))
|
||||
for domain in domains
|
||||
}
|
||||
log_task = asyncio.create_task(_async_log_pending_setups())
|
||||
await asyncio.wait(futures.values())
|
||||
log_task.cancel()
|
||||
errors = [domain for domain in domains if futures[domain].exception()]
|
||||
for domain in errors:
|
||||
exception = futures[domain].exception()
|
||||
_LOGGER.error(
|
||||
"Error setting up integration %s - received exception",
|
||||
domain,
|
||||
exc_info=(type(exception), exception, exception.__traceback__),
|
||||
integrations_to_process = [
|
||||
int_or_exc
|
||||
for int_or_exc in await asyncio.gather(
|
||||
*(
|
||||
loader.async_get_integration(hass, domain)
|
||||
for domain in old_to_resolve
|
||||
),
|
||||
return_exceptions=True,
|
||||
)
|
||||
if isinstance(int_or_exc, loader.Integration)
|
||||
]
|
||||
resolve_dependencies_tasks = [
|
||||
itg.resolve_dependencies()
|
||||
for itg in integrations_to_process
|
||||
if not itg.all_dependencies_resolved
|
||||
]
|
||||
|
||||
domains = _get_domains(hass, config)
|
||||
if resolve_dependencies_tasks:
|
||||
await asyncio.gather(*resolve_dependencies_tasks)
|
||||
|
||||
for itg in integrations_to_process:
|
||||
integration_cache[itg.domain] = itg
|
||||
|
||||
for dep in itg.all_dependencies:
|
||||
if dep in domains_to_setup:
|
||||
continue
|
||||
|
||||
domains_to_setup.add(dep)
|
||||
to_resolve.add(dep)
|
||||
|
||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||
|
||||
logging_domains = domains_to_setup & LOGGING_INTEGRATIONS
|
||||
|
||||
# Load logging as soon as possible
|
||||
if logging_domains:
|
||||
_LOGGER.info("Setting up logging: %s", logging_domains)
|
||||
await async_setup_multi_components(hass, logging_domains, config, setup_started)
|
||||
|
||||
# Start up debuggers. Start these first in case they want to wait.
|
||||
debuggers = domains & DEBUGGER_INTEGRATIONS
|
||||
debuggers = domains_to_setup & DEBUGGER_INTEGRATIONS
|
||||
|
||||
if debuggers:
|
||||
_LOGGER.debug("Starting up debuggers %s", debuggers)
|
||||
await async_setup_multi_components(debuggers)
|
||||
domains -= DEBUGGER_INTEGRATIONS
|
||||
_LOGGER.debug("Setting up debuggers: %s", debuggers)
|
||||
await async_setup_multi_components(hass, debuggers, config, setup_started)
|
||||
|
||||
# Resolve all dependencies of all components so we can find the logging
|
||||
# and integrations that need faster initialization.
|
||||
resolved_domains_task = asyncio.gather(
|
||||
*(loader.async_component_dependencies(hass, domain) for domain in domains),
|
||||
return_exceptions=True,
|
||||
)
|
||||
# calculate what components to setup in what stage
|
||||
stage_1_domains = set()
|
||||
|
||||
# Finish resolving domains
|
||||
for dep_domains in await resolved_domains_task:
|
||||
# Result is either a set or an exception. We ignore exceptions
|
||||
# It will be properly handled during setup of the domain.
|
||||
if isinstance(dep_domains, set):
|
||||
domains.update(dep_domains)
|
||||
# Find all dependencies of any dependency of any stage 1 integration that
|
||||
# we plan on loading and promote them to stage 1
|
||||
deps_promotion = STAGE_1_INTEGRATIONS
|
||||
while deps_promotion:
|
||||
old_deps_promotion = deps_promotion
|
||||
deps_promotion = set()
|
||||
|
||||
# setup components
|
||||
logging_domains = domains & LOGGING_INTEGRATIONS
|
||||
stage_1_domains = domains & STAGE_1_INTEGRATIONS
|
||||
stage_2_domains = domains - logging_domains - stage_1_domains
|
||||
for domain in old_deps_promotion:
|
||||
if domain not in domains_to_setup or domain in stage_1_domains:
|
||||
continue
|
||||
|
||||
if logging_domains:
|
||||
_LOGGER.info("Setting up %s", logging_domains)
|
||||
stage_1_domains.add(domain)
|
||||
|
||||
await async_setup_multi_components(logging_domains)
|
||||
dep_itg = integration_cache.get(domain)
|
||||
|
||||
if dep_itg is None:
|
||||
continue
|
||||
|
||||
deps_promotion.update(dep_itg.all_dependencies)
|
||||
|
||||
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
|
||||
|
||||
# Kick off loading the registries. They don't need to be awaited.
|
||||
asyncio.gather(
|
||||
@@ -405,49 +474,17 @@ async def _async_set_up_integrations(
|
||||
hass.helpers.area_registry.async_get_registry(),
|
||||
)
|
||||
|
||||
# Start setup
|
||||
if stage_1_domains:
|
||||
_LOGGER.info("Setting up %s", stage_1_domains)
|
||||
_LOGGER.info("Setting up stage 1: %s", stage_1_domains)
|
||||
await async_setup_multi_components(hass, stage_1_domains, config, setup_started)
|
||||
|
||||
await async_setup_multi_components(stage_1_domains)
|
||||
# Enables after dependencies
|
||||
async_set_domains_to_be_loaded(hass, stage_1_domains | stage_2_domains)
|
||||
|
||||
# Load all integrations
|
||||
after_dependencies: Dict[str, Set[str]] = {}
|
||||
|
||||
for int_or_exc in await asyncio.gather(
|
||||
*(loader.async_get_integration(hass, domain) for domain in stage_2_domains),
|
||||
return_exceptions=True,
|
||||
):
|
||||
# Exceptions are handled in async_setup_component.
|
||||
if isinstance(int_or_exc, loader.Integration) and int_or_exc.after_dependencies:
|
||||
after_dependencies[int_or_exc.domain] = set(int_or_exc.after_dependencies)
|
||||
|
||||
last_load = None
|
||||
while stage_2_domains:
|
||||
domains_to_load = set()
|
||||
|
||||
for domain in stage_2_domains:
|
||||
after_deps = after_dependencies.get(domain)
|
||||
# Load if integration has no after_dependencies or they are
|
||||
# all loaded
|
||||
if not after_deps or not after_deps - hass.config.components:
|
||||
domains_to_load.add(domain)
|
||||
|
||||
if not domains_to_load or domains_to_load == last_load:
|
||||
break
|
||||
|
||||
_LOGGER.debug("Setting up %s", domains_to_load)
|
||||
|
||||
await async_setup_multi_components(domains_to_load)
|
||||
|
||||
last_load = domains_to_load
|
||||
stage_2_domains -= domains_to_load
|
||||
|
||||
# These are stage 2 domains that never have their after_dependencies
|
||||
# satisfied.
|
||||
if stage_2_domains:
|
||||
_LOGGER.debug("Final set up: %s", stage_2_domains)
|
||||
|
||||
await async_setup_multi_components(stage_2_domains)
|
||||
_LOGGER.info("Setting up stage 2: %s", stage_2_domains)
|
||||
await async_setup_multi_components(hass, stage_2_domains, config, setup_started)
|
||||
|
||||
# Wrap up startup
|
||||
_LOGGER.debug("Waiting for startup to wrap up")
|
||||
|
@@ -4,5 +4,8 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": ["abodepy==0.19.0"],
|
||||
"codeowners": ["@shred86"]
|
||||
"codeowners": ["@shred86"],
|
||||
"homekit": {
|
||||
"models": ["Abode", "Iota"]
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Passwuert",
|
||||
"username": "E-Mail Adress"
|
||||
"username": "E-Mail"
|
||||
},
|
||||
"title": "F\u00ebllt \u00e4r Abode Login Informatiounen aus."
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Abode"
|
||||
}
|
@@ -4,7 +4,8 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"id": "Host-ID"
|
||||
}
|
||||
},
|
||||
"title": "W\u00e4hlen Sie einen Hub zum Hinzuf\u00fcgen aus"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"all_configured": "Aucun nouveau hub Pulse n'a \u00e9t\u00e9 d\u00e9couvert."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
@@ -8,5 +11,6 @@
|
||||
"title": "Choisissez un hub \u00e0 ajouter"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Rollease Acmeda Automate"
|
||||
}
|
@@ -71,7 +71,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
|
||||
except AdGuardHomeConnectionError as exception:
|
||||
raise ConfigEntryNotReady from exception
|
||||
|
||||
if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
if version and LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
_LOGGER.error(
|
||||
"This integration requires AdGuard Home v0.99.0 or higher to work correctly"
|
||||
)
|
||||
|
@@ -84,7 +84,7 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
errors["base"] = "connection_error"
|
||||
return await self._show_setup_form(errors)
|
||||
|
||||
if LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
if version and LooseVersion(MIN_ADGUARD_HOME_VERSION) > LooseVersion(version):
|
||||
return self.async_abort(
|
||||
reason="adguard_home_outdated",
|
||||
description_placeholders={
|
||||
@@ -105,7 +105,7 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_hassio(self, user_input=None):
|
||||
async def async_step_hassio(self, discovery_info):
|
||||
"""Prepare configuration for a Hass.io AdGuard Home add-on.
|
||||
|
||||
This flow is triggered by the discovery component.
|
||||
@@ -113,14 +113,14 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
entries = self._async_current_entries()
|
||||
|
||||
if not entries:
|
||||
self._hassio_discovery = user_input
|
||||
self._hassio_discovery = discovery_info
|
||||
return await self.async_step_hassio_confirm()
|
||||
|
||||
cur_entry = entries[0]
|
||||
|
||||
if (
|
||||
cur_entry.data[CONF_HOST] == user_input[CONF_HOST]
|
||||
and cur_entry.data[CONF_PORT] == user_input[CONF_PORT]
|
||||
cur_entry.data[CONF_HOST] == discovery_info[CONF_HOST]
|
||||
and cur_entry.data[CONF_PORT] == discovery_info[CONF_PORT]
|
||||
):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
@@ -133,8 +133,8 @@ class AdGuardHomeFlowHandler(ConfigFlow):
|
||||
cur_entry,
|
||||
data={
|
||||
**cur_entry.data,
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
CONF_HOST: discovery_info[CONF_HOST],
|
||||
CONF_PORT: discovery_info[CONF_PORT],
|
||||
},
|
||||
)
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Servidor",
|
||||
"password": "Palavra-passe",
|
||||
"username": "Nome de Utilizador"
|
||||
}
|
||||
|
@@ -23,13 +23,13 @@ class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Initialize the Agent config flow."""
|
||||
self.device_config = {}
|
||||
|
||||
async def async_step_user(self, info=None):
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle an Agent config flow."""
|
||||
errors = {}
|
||||
|
||||
if info is not None:
|
||||
host = info[CONF_HOST]
|
||||
port = info[CONF_PORT]
|
||||
if user_input is not None:
|
||||
host = user_input[CONF_HOST]
|
||||
port = user_input[CONF_PORT]
|
||||
|
||||
server_origin = generate_url(host, port)
|
||||
agent_client = Agent(server_origin, async_get_clientsession(self.hass))
|
||||
@@ -48,8 +48,8 @@ class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
self._abort_if_unique_id_configured(
|
||||
updates={
|
||||
CONF_HOST: info[CONF_HOST],
|
||||
CONF_PORT: info[CONF_PORT],
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
SERVER_URL: server_origin,
|
||||
}
|
||||
)
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"already_configured": "El dispositivo ya est\u00e1 configurado"
|
||||
},
|
||||
"error": {
|
||||
"already_in_progress": "La configuraci\u00f3n del flujo para el dispositivo ya est\u00e1 en marcha.",
|
||||
"already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en marcha.",
|
||||
"device_unavailable": "El dispositivo no est\u00e1 disponible"
|
||||
},
|
||||
"step": {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"access_token": "Vert"
|
||||
"host": "Servidor"
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API-Schl\u00fcssel",
|
||||
"api_key": "API-Schl\u00fcssel",
|
||||
"latitude": "Breitengrad",
|
||||
"longitude": "L\u00e4ngengrad",
|
||||
"name": "Name der Integration"
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API Schl\u00ebssel",
|
||||
"api_key": "API Schl\u00ebssel",
|
||||
"latitude": "Breedegrad",
|
||||
"longitude": "L\u00e4ngegrad",
|
||||
"name": "Numm vun der Installatioun"
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Airly API-n\u00f8kkel",
|
||||
"api_key": "API-n\u00f8kkel",
|
||||
"latitude": "Breddegrad",
|
||||
"longitude": "Lengdegrad",
|
||||
"name": "Navn p\u00e5 integrasjonen"
|
||||
|
@@ -3,6 +3,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "",
|
||||
"latitude": "Latitude",
|
||||
"longitude": "Longitude"
|
||||
},
|
||||
|
@@ -20,7 +20,7 @@
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "IP-Adresse/Hostname des Ger\u00e4ts",
|
||||
"password": "Ger\u00e4tekennwort"
|
||||
"password": "Passwort"
|
||||
},
|
||||
"description": "\u00dcberwachen Sie eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
||||
"title": "Konfigurieren Sie einen AirVisual Node/Pro"
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "IP Adresse / Numm vun der Unit\u00e9it",
|
||||
"password": "Passwuert vun der Unit\u00e9it"
|
||||
"password": "Passwuert"
|
||||
},
|
||||
"description": "Pers\u00e9inlech Airvisual Unit\u00e9it iwwerwaachen. Passwuert kann vum UI vum Apparat ausgelies ginn.",
|
||||
"title": "Airvisual Node/Pro ariichten"
|
||||
@@ -32,7 +32,7 @@
|
||||
"node_pro": "Airvisual Node Pro",
|
||||
"type": "Typ vun der Integratioun"
|
||||
},
|
||||
"description": "Loft Qualit\u00e9it an enger geografescher Lag iwwerwaachen.",
|
||||
"description": "Typ vun Airvisual Donn\u00e9\u00eb fir d'Iwwerwachung auswielen.",
|
||||
"title": "AirVisual konfigur\u00e9ieren"
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Enhetens IP-adresse / vertsnavn",
|
||||
"password": "Passord for enhet"
|
||||
"password": "Passord"
|
||||
},
|
||||
"description": "Overv\u00e5ke en personlig AirVisual-enhet. Passordet kan hentes fra enhetens brukergrensesnitt.",
|
||||
"title": "Konfigurer en AirVisual Node / Pro"
|
||||
|
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"latitude": "Zemepisn\u00e1 \u0161\u00edrka",
|
||||
"longitude": "Zemepisn\u00e1 d\u013a\u017eka"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from alarmdecoder import AlarmDecoder
|
||||
from adext import AdExt
|
||||
from alarmdecoder.devices import SerialDevice, SocketDevice, USBDevice
|
||||
from alarmdecoder.util import NoDeviceError
|
||||
import voluptuous as vol
|
||||
@@ -189,13 +189,13 @@ def setup(hass, config):
|
||||
if device_type == "socket":
|
||||
host = device[CONF_HOST]
|
||||
port = device[CONF_DEVICE_PORT]
|
||||
controller = AlarmDecoder(SocketDevice(interface=(host, port)))
|
||||
controller = AdExt(SocketDevice(interface=(host, port)))
|
||||
elif device_type == "serial":
|
||||
path = device[CONF_DEVICE_PATH]
|
||||
baud = device[CONF_DEVICE_BAUD]
|
||||
controller = AlarmDecoder(SerialDevice(interface=path))
|
||||
controller = AdExt(SerialDevice(interface=path))
|
||||
elif device_type == "usb":
|
||||
AlarmDecoder(USBDevice.find())
|
||||
AdExt(USBDevice.find())
|
||||
return False
|
||||
|
||||
controller.on_message += handle_message
|
||||
|
@@ -16,6 +16,7 @@ from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
@@ -108,6 +109,8 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif message.armed_away:
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif message.armed_home and (message.entry_delay_off or message.perimeter_only):
|
||||
self._state = STATE_ALARM_ARMED_NIGHT
|
||||
elif message.armed_home:
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
@@ -178,28 +181,27 @@ class AlarmDecoderAlarmPanel(AlarmControlPanelEntity):
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
if self._auto_bypass:
|
||||
self.hass.data[DATA_AD].send(f"{code!s}6#")
|
||||
self.hass.data[DATA_AD].send(f"{code!s}2")
|
||||
elif not self._code_arm_required:
|
||||
self.hass.data[DATA_AD].send("#2")
|
||||
self.hass.data[DATA_AD].arm_away(
|
||||
code=code,
|
||||
code_arm_required=self._code_arm_required,
|
||||
auto_bypass=self._auto_bypass,
|
||||
)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
if self._auto_bypass:
|
||||
self.hass.data[DATA_AD].send(f"{code!s}6#")
|
||||
self.hass.data[DATA_AD].send(f"{code!s}3")
|
||||
elif not self._code_arm_required:
|
||||
self.hass.data[DATA_AD].send("#3")
|
||||
self.hass.data[DATA_AD].arm_home(
|
||||
code=code,
|
||||
code_arm_required=self._code_arm_required,
|
||||
auto_bypass=self._auto_bypass,
|
||||
)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
if code:
|
||||
self.hass.data[DATA_AD].send(f"{code!s}7")
|
||||
elif not self._code_arm_required:
|
||||
self.hass.data[DATA_AD].send("#7")
|
||||
self.hass.data[DATA_AD].arm_night(
|
||||
code=code,
|
||||
code_arm_required=self._code_arm_required,
|
||||
auto_bypass=self._auto_bypass,
|
||||
)
|
||||
|
||||
def alarm_toggle_chime(self, code=None):
|
||||
"""Send toggle chime command."""
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "alarmdecoder",
|
||||
"name": "AlarmDecoder",
|
||||
"documentation": "https://www.home-assistant.io/integrations/alarmdecoder",
|
||||
"requirements": ["alarmdecoder==1.13.2"],
|
||||
"requirements": ["adext==0.3"],
|
||||
"codeowners": ["@ajschmidt8"]
|
||||
}
|
||||
|
@@ -222,11 +222,6 @@ class Alert(ToggleEntity):
|
||||
return STATE_ON
|
||||
return STATE_IDLE
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Hide the alert when it is not firing."""
|
||||
return not self._can_ack or not self._firing
|
||||
|
||||
async def watched_entity_change(self, entity, from_state, to_state):
|
||||
"""Determine if the alert should start or stop."""
|
||||
_LOGGER.debug("Watched entity (%s) has changed", entity)
|
||||
|
@@ -17,6 +17,7 @@ from .const import (
|
||||
CONF_ENTITY_CONFIG,
|
||||
CONF_FILTER,
|
||||
CONF_LOCALE,
|
||||
CONF_PASSWORD,
|
||||
CONF_SUPPORTED_LOCALES,
|
||||
CONF_TEXT,
|
||||
CONF_TITLE,
|
||||
@@ -56,6 +57,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: {
|
||||
CONF_FLASH_BRIEFINGS: {
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
cv.string: vol.All(
|
||||
cv.ensure_list,
|
||||
[
|
||||
@@ -67,7 +69,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_DISPLAY_URL): cv.template,
|
||||
}
|
||||
],
|
||||
)
|
||||
),
|
||||
},
|
||||
# vol.Optional here would mean we couldn't distinguish between an empty
|
||||
# smart_home: and none at all.
|
||||
|
@@ -19,6 +19,7 @@ CONF_FILTER = "filter"
|
||||
CONF_ENTITY_CONFIG = "entity_config"
|
||||
CONF_ENDPOINT = "endpoint"
|
||||
CONF_LOCALE = "locale"
|
||||
CONF_PASSWORD = "password"
|
||||
|
||||
ATTR_UID = "uid"
|
||||
ATTR_UPDATE_DATE = "updateDate"
|
||||
@@ -39,6 +40,7 @@ API_HEADER = "header"
|
||||
API_PAYLOAD = "payload"
|
||||
API_SCOPE = "scope"
|
||||
API_CHANGE = "change"
|
||||
API_PASSWORD = "password"
|
||||
|
||||
CONF_DESCRIPTION = "description"
|
||||
CONF_DISPLAY_CATEGORIES = "display_categories"
|
||||
|
@@ -1,15 +1,17 @@
|
||||
"""Support for Alexa skill service end point."""
|
||||
import copy
|
||||
import hmac
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from homeassistant.components import http
|
||||
from homeassistant.const import HTTP_NOT_FOUND
|
||||
from homeassistant.const import HTTP_NOT_FOUND, HTTP_UNAUTHORIZED
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import (
|
||||
API_PASSWORD,
|
||||
ATTR_MAIN_TEXT,
|
||||
ATTR_REDIRECTION_URL,
|
||||
ATTR_STREAM_URL,
|
||||
@@ -18,6 +20,7 @@ from .const import (
|
||||
ATTR_UPDATE_DATE,
|
||||
CONF_AUDIO,
|
||||
CONF_DISPLAY_URL,
|
||||
CONF_PASSWORD,
|
||||
CONF_TEXT,
|
||||
CONF_TITLE,
|
||||
CONF_UID,
|
||||
@@ -39,6 +42,7 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
"""Handle Alexa Flash Briefing skill requests."""
|
||||
|
||||
url = FLASH_BRIEFINGS_API_ENDPOINT
|
||||
requires_auth = False
|
||||
name = "api:alexa:flash_briefings"
|
||||
|
||||
def __init__(self, hass, flash_briefings):
|
||||
@@ -52,7 +56,20 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
"""Handle Alexa Flash Briefing request."""
|
||||
_LOGGER.debug("Received Alexa flash briefing request for: %s", briefing_id)
|
||||
|
||||
if self.flash_briefings.get(briefing_id) is None:
|
||||
if request.query.get(API_PASSWORD) is None:
|
||||
err = "No password provided for Alexa flash briefing: %s"
|
||||
_LOGGER.error(err, briefing_id)
|
||||
return b"", HTTP_UNAUTHORIZED
|
||||
|
||||
if not hmac.compare_digest(
|
||||
request.query[API_PASSWORD].encode("utf-8"),
|
||||
self.flash_briefings[CONF_PASSWORD].encode("utf-8"),
|
||||
):
|
||||
err = "Wrong password for Alexa flash briefing: %s"
|
||||
_LOGGER.error(err, briefing_id)
|
||||
return b"", HTTP_UNAUTHORIZED
|
||||
|
||||
if not isinstance(self.flash_briefings.get(briefing_id), list):
|
||||
err = "No configured Alexa flash briefing was found for: %s"
|
||||
_LOGGER.error(err, briefing_id)
|
||||
return b"", HTTP_NOT_FOUND
|
||||
|
@@ -94,12 +94,12 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
|
||||
data={"type": TYPE_LOCAL, "host": user_input["host"]},
|
||||
)
|
||||
|
||||
async def async_step_hassio(self, user_input=None):
|
||||
async def async_step_hassio(self, discovery_info):
|
||||
"""Receive a Hass.io discovery."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="already_setup")
|
||||
|
||||
self.hassio_discovery = user_input
|
||||
self.hassio_discovery = discovery_info
|
||||
|
||||
return await self.async_step_hassio_confirm()
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Almond"
|
||||
}
|
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"step": {
|
||||
"auth": {
|
||||
"description": "Follegt d\u00ebsem [Link]({authorization_url}) an <b>erlaabtt</b> den Acc\u00e8s zu \u00e4rem Ambiclimate Kont , a kommt dann zer\u00e9ck heihin an dr\u00e9ck op <b>ofsch\u00e9cken</b> hei \u00ebnnen.\n(Stellt s\u00e9cher dass den Type vun Callback {cb_url} ass.)",
|
||||
"description": "Follegt d\u00ebsem [Link]({authorization_url}) an ***erlaabt** den Acc\u00e8s zu \u00e4rem Ambiclimate Kont , a kommt dann zer\u00e9ck heihin an dr\u00e9ck op **ofsch\u00e9cken** hei \u00ebnnen.\n(Stellt s\u00e9cher dass den Type vun Callback {cb_url} ass.)",
|
||||
"title": "Ambiclimate authentifiz\u00e9ieren"
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Ambiclimate"
|
||||
}
|
@@ -10,6 +10,7 @@ from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_LOCATION,
|
||||
ATTR_NAME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_API_KEY,
|
||||
DEGREE,
|
||||
@@ -126,6 +127,8 @@ TYPE_TEMPF = "tempf"
|
||||
TYPE_TEMPINF = "tempinf"
|
||||
TYPE_TOTALRAININ = "totalrainin"
|
||||
TYPE_UV = "uv"
|
||||
TYPE_PM25 = "pm25"
|
||||
TYPE_PM25_24H = "pm25_24h"
|
||||
TYPE_WEEKLYRAININ = "weeklyrainin"
|
||||
TYPE_WINDDIR = "winddir"
|
||||
TYPE_WINDDIR_AVG10M = "winddir_avg10m"
|
||||
@@ -218,6 +221,13 @@ SENSOR_TYPES = {
|
||||
TYPE_TEMPINF: ("Inside Temp", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"),
|
||||
TYPE_TOTALRAININ: ("Lifetime Rain", "in", TYPE_SENSOR, None),
|
||||
TYPE_UV: ("uv", "Index", TYPE_SENSOR, None),
|
||||
TYPE_PM25: ("PM25", CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, TYPE_SENSOR, None),
|
||||
TYPE_PM25_24H: (
|
||||
"PM25 24h Avg",
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
TYPE_SENSOR,
|
||||
None,
|
||||
),
|
||||
TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None),
|
||||
TYPE_WINDDIR: ("Wind Dir", DEGREE, TYPE_SENSOR, None),
|
||||
TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", DEGREE, TYPE_SENSOR, None),
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"api_key": "API Schl\u00fcssel",
|
||||
"app_key": "Anwendungsschl\u00fcssel"
|
||||
},
|
||||
"title": "Gib deine Informationen ein"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Ambient PWS"
|
||||
}
|
@@ -130,6 +130,10 @@ class CannotSnapshot(Exception):
|
||||
"""Conditions are not valid for taking a snapshot."""
|
||||
|
||||
|
||||
class AmcrestCommandFailed(Exception):
|
||||
"""Amcrest camera command did not work."""
|
||||
|
||||
|
||||
class AmcrestCam(Camera):
|
||||
"""An implementation of an Amcrest IP camera."""
|
||||
|
||||
@@ -367,12 +371,12 @@ class AmcrestCam(Camera):
|
||||
self._model = resp.split("=")[-1]
|
||||
else:
|
||||
self._model = "unknown"
|
||||
self.is_streaming = self._api.video_enabled
|
||||
self._is_recording = self._api.record_mode == "Manual"
|
||||
self._motion_detection_enabled = self._api.is_motion_detector_on()
|
||||
self._audio_enabled = self._api.audio_enabled
|
||||
self._motion_recording_enabled = self._api.is_record_on_motion_detection()
|
||||
self._color_bw = _CBW[self._api.day_night_color]
|
||||
self.is_streaming = self._get_video()
|
||||
self._is_recording = self._get_recording()
|
||||
self._motion_detection_enabled = self._get_motion_detection()
|
||||
self._audio_enabled = self._get_audio()
|
||||
self._motion_recording_enabled = self._get_motion_recording()
|
||||
self._color_bw = self._get_color_mode()
|
||||
self._rtsp_url = self._api.rtsp_url(typeno=self._resolution)
|
||||
except AmcrestError as error:
|
||||
log_update_error(_LOGGER, "get", self.name, "camera attributes", error)
|
||||
@@ -384,11 +388,11 @@ class AmcrestCam(Camera):
|
||||
|
||||
def turn_off(self):
|
||||
"""Turn off camera."""
|
||||
self._enable_video_stream(False)
|
||||
self._enable_video(False)
|
||||
|
||||
def turn_on(self):
|
||||
"""Turn on camera."""
|
||||
self._enable_video_stream(True)
|
||||
self._enable_video(True)
|
||||
|
||||
def enable_motion_detection(self):
|
||||
"""Enable motion detection in the camera."""
|
||||
@@ -465,28 +469,53 @@ class AmcrestCam(Camera):
|
||||
|
||||
# Methods to send commands to Amcrest camera and handle errors
|
||||
|
||||
def _enable_video_stream(self, enable):
|
||||
def _change_setting(self, value, attr, description, action="set"):
|
||||
func = description.replace(" ", "_")
|
||||
description = f"camera {description} to {value}"
|
||||
tries = 3
|
||||
while True:
|
||||
try:
|
||||
getattr(self, f"_set_{func}")(value)
|
||||
new_value = getattr(self, f"_get_{func}")()
|
||||
if new_value != value:
|
||||
raise AmcrestCommandFailed
|
||||
except (AmcrestError, AmcrestCommandFailed) as error:
|
||||
if tries == 1:
|
||||
log_update_error(_LOGGER, action, self.name, description, error)
|
||||
return
|
||||
log_update_error(
|
||||
_LOGGER, action, self.name, description, error, logging.DEBUG
|
||||
)
|
||||
else:
|
||||
if attr:
|
||||
setattr(self, attr, new_value)
|
||||
self.schedule_update_ha_state()
|
||||
return
|
||||
tries -= 1
|
||||
|
||||
def _get_video(self):
|
||||
return self._api.video_enabled
|
||||
|
||||
def _set_video(self, enable):
|
||||
self._api.video_enabled = enable
|
||||
|
||||
def _enable_video(self, enable):
|
||||
"""Enable or disable camera video stream."""
|
||||
# Given the way the camera's state is determined by
|
||||
# is_streaming and is_recording, we can't leave
|
||||
# recording on if video stream is being turned off.
|
||||
if self.is_recording and not enable:
|
||||
self._enable_recording(False)
|
||||
try:
|
||||
self._api.video_enabled = enable
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"camera video stream",
|
||||
error,
|
||||
)
|
||||
else:
|
||||
self.is_streaming = enable
|
||||
self.schedule_update_ha_state()
|
||||
self._change_setting(enable, "is_streaming", "video")
|
||||
if self._control_light:
|
||||
self._enable_light(self._audio_enabled or self.is_streaming)
|
||||
self._change_light()
|
||||
|
||||
def _get_recording(self):
|
||||
return self._api.record_mode == "Manual"
|
||||
|
||||
def _set_recording(self, enable):
|
||||
rec_mode = {"Automatic": 0, "Manual": 1}
|
||||
self._api.record_mode = rec_mode["Manual" if enable else "Automatic"]
|
||||
|
||||
def _enable_recording(self, enable):
|
||||
"""Turn recording on or off."""
|
||||
@@ -494,86 +523,56 @@ class AmcrestCam(Camera):
|
||||
# is_streaming and is_recording, we can't leave
|
||||
# video stream off if recording is being turned on.
|
||||
if not self.is_streaming and enable:
|
||||
self._enable_video_stream(True)
|
||||
rec_mode = {"Automatic": 0, "Manual": 1}
|
||||
try:
|
||||
self._api.record_mode = rec_mode["Manual" if enable else "Automatic"]
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"camera recording",
|
||||
error,
|
||||
)
|
||||
else:
|
||||
self._is_recording = enable
|
||||
self.schedule_update_ha_state()
|
||||
self._enable_video(True)
|
||||
self._change_setting(enable, "_is_recording", "recording")
|
||||
|
||||
def _get_motion_detection(self):
|
||||
return self._api.is_motion_detector_on()
|
||||
|
||||
def _set_motion_detection(self, enable):
|
||||
self._api.motion_detection = str(enable).lower()
|
||||
|
||||
def _enable_motion_detection(self, enable):
|
||||
"""Enable or disable motion detection."""
|
||||
try:
|
||||
self._api.motion_detection = str(enable).lower()
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"camera motion detection",
|
||||
error,
|
||||
)
|
||||
else:
|
||||
self._motion_detection_enabled = enable
|
||||
self.schedule_update_ha_state()
|
||||
self._change_setting(enable, "_motion_detection_enabled", "motion detection")
|
||||
|
||||
def _get_audio(self):
|
||||
return self._api.audio_enabled
|
||||
|
||||
def _set_audio(self, enable):
|
||||
self._api.audio_enabled = enable
|
||||
|
||||
def _enable_audio(self, enable):
|
||||
"""Enable or disable audio stream."""
|
||||
try:
|
||||
self._api.audio_enabled = enable
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"camera audio stream",
|
||||
error,
|
||||
)
|
||||
else:
|
||||
self._audio_enabled = enable
|
||||
self.schedule_update_ha_state()
|
||||
self._change_setting(enable, "_audio_enabled", "audio")
|
||||
if self._control_light:
|
||||
self._enable_light(self._audio_enabled or self.is_streaming)
|
||||
self._change_light()
|
||||
|
||||
def _enable_light(self, enable):
|
||||
def _get_indicator_light(self):
|
||||
return "true" in self._api.command(
|
||||
"configManager.cgi?action=getConfig&name=LightGlobal"
|
||||
).content.decode("utf-8")
|
||||
|
||||
def _set_indicator_light(self, enable):
|
||||
self._api.command(
|
||||
f"configManager.cgi?action=setConfig&LightGlobal[0].Enable={str(enable).lower()}"
|
||||
)
|
||||
|
||||
def _change_light(self):
|
||||
"""Enable or disable indicator light."""
|
||||
try:
|
||||
self._api.command(
|
||||
f"configManager.cgi?action=setConfig&LightGlobal[0].Enable={str(enable).lower()}"
|
||||
)
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"indicator light",
|
||||
error,
|
||||
)
|
||||
self._change_setting(
|
||||
self._audio_enabled or self.is_streaming, None, "indicator light"
|
||||
)
|
||||
|
||||
def _get_motion_recording(self):
|
||||
return self._api.is_record_on_motion_detection()
|
||||
|
||||
def _set_motion_recording(self, enable):
|
||||
self._api.motion_recording = str(enable).lower()
|
||||
|
||||
def _enable_motion_recording(self, enable):
|
||||
"""Enable or disable motion recording."""
|
||||
try:
|
||||
self._api.motion_recording = str(enable).lower()
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER,
|
||||
"enable" if enable else "disable",
|
||||
self.name,
|
||||
"camera motion recording",
|
||||
error,
|
||||
)
|
||||
else:
|
||||
self._motion_recording_enabled = enable
|
||||
self.schedule_update_ha_state()
|
||||
self._change_setting(enable, "_motion_recording_enabled", "motion recording")
|
||||
|
||||
def _goto_preset(self, preset):
|
||||
"""Move camera position and zoom to preset."""
|
||||
@@ -584,17 +583,15 @@ class AmcrestCam(Camera):
|
||||
_LOGGER, "move", self.name, f"camera to preset {preset}", error
|
||||
)
|
||||
|
||||
def _get_color_mode(self):
|
||||
return _CBW[self._api.day_night_color]
|
||||
|
||||
def _set_color_mode(self, cbw):
|
||||
self._api.day_night_color = _CBW.index(cbw)
|
||||
|
||||
def _set_color_bw(self, cbw):
|
||||
"""Set camera color mode."""
|
||||
try:
|
||||
self._api.day_night_color = _CBW.index(cbw)
|
||||
except AmcrestError as error:
|
||||
log_update_error(
|
||||
_LOGGER, "set", self.name, f"camera color mode to {cbw}", error
|
||||
)
|
||||
else:
|
||||
self._color_bw = cbw
|
||||
self.schedule_update_ha_state()
|
||||
self._change_setting(cbw, "_color_bw", "color mode")
|
||||
|
||||
def _start_tour(self, start):
|
||||
"""Start camera tour."""
|
||||
|
@@ -1,4 +1,6 @@
|
||||
"""Helpers for amcrest component."""
|
||||
import logging
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
@@ -7,9 +9,10 @@ def service_signal(service, *args):
|
||||
return "_".join([DOMAIN, service, *args])
|
||||
|
||||
|
||||
def log_update_error(logger, action, name, entity_type, error):
|
||||
def log_update_error(logger, action, name, entity_type, error, level=logging.ERROR):
|
||||
"""Log an update error."""
|
||||
logger.error(
|
||||
logger.log(
|
||||
level,
|
||||
"Could not %s %s %s due to error: %s",
|
||||
action,
|
||||
name,
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"requirements": [
|
||||
"adb-shell==0.1.3",
|
||||
"androidtv==0.0.41",
|
||||
"androidtv==0.0.43",
|
||||
"pure-python-adb==0.2.2.dev0"
|
||||
],
|
||||
"codeowners": ["@JeffLIrion"]
|
||||
|
@@ -5,27 +5,15 @@ import logging
|
||||
from arcam.fmj import ConnectionFailed
|
||||
from arcam.fmj.client import Client
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PORT,
|
||||
CONF_SCAN_INTERVAL,
|
||||
CONF_ZONE,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
SERVICE_TURN_ON,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
|
||||
from .const import (
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
DOMAIN_DATA_CONFIG,
|
||||
DOMAIN_DATA_ENTRIES,
|
||||
DOMAIN_DATA_TASKS,
|
||||
SIGNAL_CLIENT_DATA,
|
||||
@@ -35,44 +23,7 @@ from .const import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _optional_zone(value):
|
||||
if value:
|
||||
return ZONE_SCHEMA(value)
|
||||
return ZONE_SCHEMA({})
|
||||
|
||||
|
||||
def _zone_name_validator(config):
|
||||
for zone, zone_config in config[CONF_ZONE].items():
|
||||
if CONF_NAME not in zone_config:
|
||||
zone_config[
|
||||
CONF_NAME
|
||||
] = f"{DEFAULT_NAME} ({config[CONF_HOST]}:{config[CONF_PORT]}) - {zone}"
|
||||
return config
|
||||
|
||||
|
||||
ZONE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(SERVICE_TURN_ON): cv.SERVICE_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema(
|
||||
vol.All(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.positive_int,
|
||||
vol.Optional(CONF_ZONE, default={1: _optional_zone(None)}): {
|
||||
vol.In([1, 2]): _optional_zone
|
||||
},
|
||||
vol.Optional(
|
||||
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
|
||||
): cv.positive_int,
|
||||
},
|
||||
_zone_name_validator,
|
||||
)
|
||||
)
|
||||
CONFIG_SCHEMA = cv.deprecated(DOMAIN, invalidation_version="0.115")
|
||||
|
||||
|
||||
async def _await_cancel(task):
|
||||
@@ -83,27 +34,10 @@ async def _await_cancel(task):
|
||||
pass
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{DOMAIN: vol.All(cv.ensure_list, [DEVICE_SCHEMA])}, extra=vol.ALLOW_EXTRA
|
||||
)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
"""Set up the component."""
|
||||
hass.data[DOMAIN_DATA_ENTRIES] = {}
|
||||
hass.data[DOMAIN_DATA_TASKS] = {}
|
||||
hass.data[DOMAIN_DATA_CONFIG] = {}
|
||||
|
||||
for device in config[DOMAIN]:
|
||||
hass.data[DOMAIN_DATA_CONFIG][(device[CONF_HOST], device[CONF_PORT])] = device
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_IMPORT},
|
||||
data={CONF_HOST: device[CONF_HOST], CONF_PORT: device[CONF_PORT]},
|
||||
)
|
||||
)
|
||||
|
||||
async def _stop(_):
|
||||
asyncio.gather(
|
||||
@@ -116,21 +50,12 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistantType, entry: config_entries.ConfigEntry):
|
||||
"""Set up an access point from a config entry."""
|
||||
"""Set up config entry."""
|
||||
entries = hass.data[DOMAIN_DATA_ENTRIES]
|
||||
tasks = hass.data[DOMAIN_DATA_TASKS]
|
||||
|
||||
client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
|
||||
|
||||
config = hass.data[DOMAIN_DATA_CONFIG].get(
|
||||
(entry.data[CONF_HOST], entry.data[CONF_PORT]),
|
||||
DEVICE_SCHEMA(
|
||||
{CONF_HOST: entry.data[CONF_HOST], CONF_PORT: entry.data[CONF_PORT]}
|
||||
),
|
||||
)
|
||||
tasks = hass.data.setdefault(DOMAIN_DATA_TASKS, {})
|
||||
|
||||
hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id] = {
|
||||
"client": client,
|
||||
"config": config,
|
||||
}
|
||||
entries[entry.entry_id] = client
|
||||
|
||||
task = asyncio.create_task(_run_client(hass, client, DEFAULT_SCAN_INTERVAL))
|
||||
tasks[entry.entry_id] = task
|
||||
|
@@ -1,27 +1,102 @@
|
||||
"""Config flow to configure the Arcam FMJ component."""
|
||||
from operator import itemgetter
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from arcam.fmj.client import Client, ConnectionFailed
|
||||
from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.ssdp import ATTR_SSDP_LOCATION, ATTR_UPNP_UDN
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DEFAULT_NAME, DEFAULT_PORT, DOMAIN, DOMAIN_DATA_ENTRIES
|
||||
|
||||
_GETKEY = itemgetter(CONF_HOST, CONF_PORT)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_entry_client(hass, entry):
|
||||
"""Retrieve client associated with a config entry."""
|
||||
return hass.data[DOMAIN_DATA_ENTRIES][entry.entry_id]
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class ArcamFmjFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle a SimpliSafe config flow."""
|
||||
"""Handle config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
entries = self.hass.config_entries.async_entries(DOMAIN)
|
||||
import_key = _GETKEY(import_config)
|
||||
for entry in entries:
|
||||
if _GETKEY(entry.data) == import_key:
|
||||
return self.async_abort(reason="already_setup")
|
||||
async def _async_set_unique_id_and_update(self, host, port, uuid):
|
||||
await self.async_set_unique_id(uuid)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port})
|
||||
|
||||
return self.async_create_entry(title="Arcam FMJ", data=import_config)
|
||||
async def _async_check_and_create(self, host, port):
|
||||
client = Client(host, port)
|
||||
try:
|
||||
await client.start()
|
||||
except ConnectionFailed:
|
||||
return self.async_abort(reason="unable_to_connect")
|
||||
finally:
|
||||
await client.stop()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"{DEFAULT_NAME} ({host})", data={CONF_HOST: host, CONF_PORT: port},
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a discovered device."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
uuid = await get_uniqueid_from_host(
|
||||
async_get_clientsession(self.hass), user_input[CONF_HOST]
|
||||
)
|
||||
if uuid:
|
||||
await self._async_set_unique_id_and_update(
|
||||
user_input[CONF_HOST], user_input[CONF_PORT], uuid
|
||||
)
|
||||
|
||||
return await self._async_check_and_create(
|
||||
user_input[CONF_HOST], user_input[CONF_PORT]
|
||||
)
|
||||
|
||||
fields = {
|
||||
vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
|
||||
}
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=vol.Schema(fields), errors=errors
|
||||
)
|
||||
|
||||
async def async_step_confirm(self, user_input=None):
|
||||
"""Handle user-confirmation of discovered node."""
|
||||
context = self.context # pylint: disable=no-member
|
||||
placeholders = {
|
||||
"host": context[CONF_HOST],
|
||||
}
|
||||
context["title_placeholders"] = placeholders
|
||||
|
||||
if user_input is not None:
|
||||
return await self._async_check_and_create(
|
||||
context[CONF_HOST], context[CONF_PORT]
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm", description_placeholders=placeholders
|
||||
)
|
||||
|
||||
async def async_step_ssdp(self, discovery_info):
|
||||
"""Handle a discovered device."""
|
||||
host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname
|
||||
port = DEFAULT_PORT
|
||||
uuid = get_uniqueid_from_udn(discovery_info[ATTR_UPNP_UDN])
|
||||
|
||||
await self._async_set_unique_id_and_update(host, port, uuid)
|
||||
|
||||
context = self.context # pylint: disable=no-member
|
||||
context[CONF_HOST] = host
|
||||
context[CONF_PORT] = DEFAULT_PORT
|
||||
return await self.async_step_confirm()
|
||||
|
@@ -13,4 +13,3 @@ DEFAULT_SCAN_INTERVAL = 5
|
||||
|
||||
DOMAIN_DATA_ENTRIES = f"{DOMAIN}.entries"
|
||||
DOMAIN_DATA_TASKS = f"{DOMAIN}.tasks"
|
||||
DOMAIN_DATA_CONFIG = f"{DOMAIN}.config"
|
||||
|
@@ -1,8 +1,14 @@
|
||||
{
|
||||
"domain": "arcam_fmj",
|
||||
"name": "Arcam FMJ Receivers",
|
||||
"config_flow": false,
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
|
||||
"requirements": ["arcam-fmj==0.4.6"],
|
||||
"requirements": ["arcam-fmj==0.5.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
"manufacturer": "ARCAM"
|
||||
}
|
||||
],
|
||||
"codeowners": ["@elupus"]
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
"""Arcam media player."""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from arcam.fmj import DecodeMode2CH, DecodeModeMCH, IncomingAudioFormat, SourceCodes
|
||||
from arcam.fmj.state import State
|
||||
@@ -17,21 +16,13 @@ from homeassistant.components.media_player.const import (
|
||||
SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_NAME,
|
||||
CONF_ZONE,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.service import async_call_from_config
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .config_flow import get_entry_client
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DOMAIN_DATA_ENTRIES,
|
||||
EVENT_TURN_ON,
|
||||
SIGNAL_CLIENT_DATA,
|
||||
SIGNAL_CLIENT_STARTED,
|
||||
@@ -47,19 +38,17 @@ async def async_setup_entry(
|
||||
async_add_entities,
|
||||
):
|
||||
"""Set up the configuration entry."""
|
||||
data = hass.data[DOMAIN_DATA_ENTRIES][config_entry.entry_id]
|
||||
client = data["client"]
|
||||
config = data["config"]
|
||||
|
||||
client = get_entry_client(hass, config_entry)
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
ArcamFmj(
|
||||
config_entry.title,
|
||||
State(client, zone),
|
||||
config_entry.unique_id or config_entry.entry_id,
|
||||
zone_config[CONF_NAME],
|
||||
zone_config.get(SERVICE_TURN_ON),
|
||||
)
|
||||
for zone, zone_config in config[CONF_ZONE].items()
|
||||
for zone in [1, 2]
|
||||
],
|
||||
True,
|
||||
)
|
||||
@@ -71,13 +60,13 @@ class ArcamFmj(MediaPlayerEntity):
|
||||
"""Representation of a media device."""
|
||||
|
||||
def __init__(
|
||||
self, state: State, uuid: str, name: str, turn_on: Optional[ConfigType]
|
||||
self, device_name, state: State, uuid: str,
|
||||
):
|
||||
"""Initialize device."""
|
||||
self._state = state
|
||||
self._device_name = device_name
|
||||
self._name = f"{device_name} - Zone: {state.zn}"
|
||||
self._uuid = uuid
|
||||
self._name = name
|
||||
self._turn_on = turn_on
|
||||
self._support = (
|
||||
SUPPORT_SELECT_SOURCE
|
||||
| SUPPORT_VOLUME_SET
|
||||
@@ -102,6 +91,11 @@ class ArcamFmj(MediaPlayerEntity):
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._state.zn == 1
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique identifier if known."""
|
||||
@@ -111,8 +105,12 @@ class ArcamFmj(MediaPlayerEntity):
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._state.client.host, self._state.client.port)},
|
||||
"model": "FMJ",
|
||||
"name": self._device_name,
|
||||
"identifiers": {
|
||||
(DOMAIN, self._uuid),
|
||||
(DOMAIN, self._state.client.host, self._state.client.port),
|
||||
},
|
||||
"model": "Arcam FMJ AVR",
|
||||
"manufacturer": "Arcam",
|
||||
}
|
||||
|
||||
@@ -229,15 +227,6 @@ class ArcamFmj(MediaPlayerEntity):
|
||||
if self._state.get_power() is not None:
|
||||
_LOGGER.debug("Turning on device using connection")
|
||||
await self._state.set_power(True)
|
||||
elif self._turn_on:
|
||||
_LOGGER.debug("Turning on device using service call")
|
||||
await async_call_from_config(
|
||||
self.hass,
|
||||
self._turn_on,
|
||||
variables=None,
|
||||
blocking=True,
|
||||
validate_config=False,
|
||||
)
|
||||
else:
|
||||
_LOGGER.debug("Firing event to turn on device")
|
||||
self.hass.bus.async_fire(EVENT_TURN_ON, {ATTR_ENTITY_ID: self.entity_id})
|
||||
|
@@ -1,7 +1,28 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device was already setup.",
|
||||
"already_in_progress": "Config flow for device is already in progress.",
|
||||
"unable_to_connect": "Unable to connect to device."
|
||||
},
|
||||
"error": {},
|
||||
"flow_title": "Arcam FMJ on {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"port": "[%key:common::config_flow::data::port%]"
|
||||
},
|
||||
"description": "Please enter the host name or IP address of device."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} was requested to turn on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "El dispositiu ja s'ha configurat.",
|
||||
"already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.",
|
||||
"unable_to_connect": "No es pot connectar amb el dispositiu."
|
||||
},
|
||||
"flow_title": "Arcam FMJ a {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Vols afegir l'Arcam FMJ `{host}` a Home Assistant?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Amfitri\u00f3",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "Introdueix el nom de l'amfitri\u00f3 o l'adre\u00e7a IP del dispositiu."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "S'ha sol\u00b7licitat l'activaci\u00f3 de {entity_name}"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,4 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"unable_to_connect": "Es konnte keine Verbindung mit dem Ger\u00e4t hergestellt werden."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "Bitte gib den Hostnamen oder die IP-Adresse des Ger\u00e4ts ein."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} wurde zum Einschalten aufgefordert"
|
||||
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Device was already setup.",
|
||||
"already_in_progress": "Config flow for device is already in progress.",
|
||||
"unable_to_connect": "Unable to connect to device."
|
||||
},
|
||||
"flow_title": "Arcam FMJ on {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "Please enter the host name or IP address of device."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} was requested to turn on"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "El dispositivo ya est\u00e1 configurado.",
|
||||
"already_in_progress": "El flujo de configuraci\u00f3n para el dispositivo ya est\u00e1 en marcha.",
|
||||
"unable_to_connect": "No se puede conectar con el dispositivo."
|
||||
},
|
||||
"flow_title": "Arcam FMJ en {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u00bfQuieres a\u00f1adir el Arcam FMJ en `{host}` a Home Assistant?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Puerto"
|
||||
},
|
||||
"description": "Por favor, introduce el nombre del host o la direcci\u00f3n IP del dispositivo."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "Se solicit\u00f3 encender {entity_name}"
|
||||
|
@@ -1,3 +1,27 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "L'appareil \u00e9tait d\u00e9j\u00e0 configur\u00e9.",
|
||||
"already_in_progress": "Le flux de configuration de l'appareil est d\u00e9j\u00e0 en cours.",
|
||||
"unable_to_connect": "Impossible de se connecter au p\u00e9riph\u00e9rique."
|
||||
},
|
||||
"error": {
|
||||
"one": "Vide",
|
||||
"other": "Vide"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "H\u00f4te",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "Veuillez saisir le nom d\u2019h\u00f4te ou l\u2019adresse IP du p\u00e9riph\u00e9rique."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "Il a \u00e9t\u00e9 demand\u00e9 \u00e0 {nom_de_l'entit\u00e9} de s'allumer"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,28 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Il dispositivo era gi\u00e0 configurato.",
|
||||
"already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.",
|
||||
"unable_to_connect": "Impossibile connettersi al dispositivo."
|
||||
},
|
||||
"error": {
|
||||
"one": "uno",
|
||||
"other": "altri"
|
||||
},
|
||||
"flow_title": "Arcam FMJ su {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Vuoi aggiungere Arcam FMJ su `{host}` a Home Assistant?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Porta"
|
||||
},
|
||||
"description": "Inserisci il nome host o l'indirizzo IP del dispositivo."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "\u00c8 stato richiesto di attivare {entity_name}"
|
||||
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||
"already_in_progress": "\uae30\uae30 \uad6c\uc131\uc774 \uc774\ubbf8 \uc9c4\ud589 \uc911\uc785\ub2c8\ub2e4.",
|
||||
"unable_to_connect": "\uae30\uae30\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"flow_title": "Arcam FMJ: {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Home Assistant \uc5d0 Arcam FMJ `{host}` \uc744(\ub97c) \ucd94\uac00\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "\ud638\uc2a4\ud2b8",
|
||||
"port": "\ud3ec\ud2b8"
|
||||
},
|
||||
"description": "\uae30\uae30\uc758 \ud638\uc2a4\ud2b8 \uc774\ub984 \ub610\ub294 IP \uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} \uc774(\uac00) \ucf1c\uc9c0\ub3c4\ub85d \uc694\uccad\ub418\uc5c8\uc744 \ub54c"
|
||||
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Apparat ass scho konfigur\u00e9iert",
|
||||
"already_in_progress": "Konfiguratioun's Oflaf fir den Apparat ass schonn am gaangen.",
|
||||
"unable_to_connect": "Keng Verbindung mam Apparat m\u00e9iglech."
|
||||
},
|
||||
"flow_title": "Arcam FMJ um {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Soll den Arcam FMJ um `{host}` am Home Assistant dob\u00e4i gesaat ginn?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port"
|
||||
},
|
||||
"description": "G\u00ebff den Numm oder IP-Adress vum Apparat un."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} soll ugeschalt ginn"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,4 +1,20 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Enheten var allerede konfigurert.",
|
||||
"already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.",
|
||||
"unable_to_connect": "Kan ikke koble til enheten."
|
||||
},
|
||||
"flow_title": "Arcam FMJ p\u00e5 {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "Vil du legge Arcam FMJ p\u00e5 ` {host} ` til Home Assistant? "
|
||||
},
|
||||
"user": {
|
||||
"description": "Vennligst skriv inn vertsnavnet eller IP-adressen til enheten."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} ble bedt om \u00e5 sl\u00e5 p\u00e5"
|
||||
|
@@ -1,4 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Nazwa hosta lub adres IP",
|
||||
"port": "Port"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} zostanie poproszony o w\u0142\u0105czenie"
|
||||
|
@@ -3,8 +3,8 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Adgangskode",
|
||||
"username": "Brugernavn"
|
||||
"host": "Servidor",
|
||||
"port": "Porto"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.",
|
||||
"already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.",
|
||||
"unable_to_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443."
|
||||
},
|
||||
"flow_title": "Arcam FMJ {host}",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c Arcam FMJ `{host}`?"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "\u0425\u043e\u0441\u0442",
|
||||
"port": "\u041f\u043e\u0440\u0442"
|
||||
},
|
||||
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430."
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "\u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 {entity_name}"
|
||||
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"title": "Arcam FMJ"
|
||||
}
|
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u8a2d\u5099\u5df2\u8a2d\u5b9a\u3002",
|
||||
"already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002",
|
||||
"unable_to_connect": "\u7121\u6cd5\u9023\u7dda\u81f3\u8a2d\u5099\u3002"
|
||||
},
|
||||
"flow_title": "Arcam FMJ \uff08{host}\uff09",
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u662f\u5426\u8981\u5c07 Arcam FMJ `{host}` \u65b0\u589e\u81f3 Home Assistant\uff1f"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "\u4e3b\u6a5f\u7aef",
|
||||
"port": "\u901a\u8a0a\u57e0"
|
||||
},
|
||||
"description": "\u8acb\u8f38\u5165\u4e3b\u6a5f\u7aef\u540d\u7a31\u6216 Heos \u8a2d\u5099 IP \u4f4d\u5740\u3002"
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
"trigger_type": {
|
||||
"turn_on": "{entity_name} \u4f9d\u9700\u6c42\u958b\u555f"
|
||||
|
@@ -24,6 +24,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
self.last_results = {}
|
||||
self.success_init = False
|
||||
self.connection = api
|
||||
self._connect_error = False
|
||||
|
||||
async def async_connect(self):
|
||||
"""Initialize connection to the router."""
|
||||
@@ -49,4 +50,15 @@ class AsusWrtDeviceScanner(DeviceScanner):
|
||||
"""
|
||||
_LOGGER.debug("Checking Devices")
|
||||
|
||||
self.last_results = await self.connection.async_get_connected_devices()
|
||||
try:
|
||||
self.last_results = await self.connection.async_get_connected_devices()
|
||||
if self._connect_error:
|
||||
self._connect_error = False
|
||||
_LOGGER.error("Reconnected to ASUS router for device update")
|
||||
|
||||
except OSError as err:
|
||||
if not self._connect_error:
|
||||
self._connect_error = True
|
||||
_LOGGER.error(
|
||||
"Error connecting to ASUS router for device update: %s", err
|
||||
)
|
||||
|
@@ -2,6 +2,6 @@
|
||||
"domain": "asuswrt",
|
||||
"name": "ASUSWRT",
|
||||
"documentation": "https://www.home-assistant.io/integrations/asuswrt",
|
||||
"requirements": ["aioasuswrt==1.2.5"],
|
||||
"requirements": ["aioasuswrt==1.2.6"],
|
||||
"codeowners": ["@kennedyshead"]
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ class AsuswrtSensor(Entity):
|
||||
self._devices = None
|
||||
self._rates = None
|
||||
self._speed = None
|
||||
self._connect_error = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -62,9 +63,23 @@ class AsuswrtSensor(Entity):
|
||||
|
||||
async def async_update(self):
|
||||
"""Fetch status from asuswrt."""
|
||||
self._devices = await self._api.async_get_connected_devices()
|
||||
self._rates = await self._api.async_get_bytes_total()
|
||||
self._speed = await self._api.async_get_current_transfer_rates()
|
||||
try:
|
||||
self._devices = await self._api.async_get_connected_devices()
|
||||
self._rates = await self._api.async_get_bytes_total()
|
||||
self._speed = await self._api.async_get_current_transfer_rates()
|
||||
if self._connect_error:
|
||||
self._connect_error = False
|
||||
_LOGGER.error(
|
||||
"Reconnected to ASUS router for %s update", self.entity_id
|
||||
)
|
||||
except OSError as err:
|
||||
if not self._connect_error:
|
||||
self._connect_error = True
|
||||
_LOGGER.error(
|
||||
"Error connecting to ASUS router for %s update: %s",
|
||||
self.entity_id,
|
||||
err,
|
||||
)
|
||||
|
||||
|
||||
class AsuswrtDevicesSensor(AsuswrtSensor):
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Nur ein Atag-Ger\u00e4t kann mit Home Assistant verbunden werden."
|
||||
"already_configured": "Dieses Ger\u00e4t wurde bereits zu HomeAssistant hinzugef\u00fcgt"
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Verbindung fehlgeschlagen, versuchen Sie es erneut"
|
||||
@@ -11,7 +11,7 @@
|
||||
"data": {
|
||||
"email": "Email (Optional)",
|
||||
"host": "Host",
|
||||
"port": "Port (10000)"
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "Stellen Sie eine Verbindung zum Ger\u00e4t her"
|
||||
}
|
||||
|
@@ -4,7 +4,8 @@
|
||||
"already_configured": "Un seul appareil Atag peut \u00eatre ajout\u00e9 \u00e0 Home Assistant"
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Impossible de se connecter, veuillez r\u00e9essayer"
|
||||
"connection_error": "Impossible de se connecter, veuillez r\u00e9essayer",
|
||||
"unauthorized": "Pairage refus\u00e9, v\u00e9rifiez la demande d'authentification de l'appareil"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Home Assistant \uc5d0\ub294 \ud558\ub098\uc758 Atag \uae30\uae30\ub9cc \ucd94\uac00\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4"
|
||||
"already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 HomeAssistant \uc5d0 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "N\u00ebmmen 1 Atag Apparat kann am Home Assistant dob\u00e4igesat ginn"
|
||||
"already_configured": "D\u00ebsen Apparat ass schonn am Home Assistant dob\u00e4igesat ginn"
|
||||
},
|
||||
"error": {
|
||||
"connection_error": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.",
|
||||
@@ -12,7 +12,7 @@
|
||||
"data": {
|
||||
"email": "E-Mail (Optionell)",
|
||||
"host": "Apparat",
|
||||
"port": "Port (10000)"
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "Mam Apparat verbannen"
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"enable_sensors": "Trafik sens\u00f6rleri ekleyin"
|
||||
"host": "Servidor"
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,7 +3,6 @@
|
||||
"name": "Auth",
|
||||
"documentation": "https://www.home-assistant.io/integrations/auth",
|
||||
"dependencies": ["http"],
|
||||
"after_dependencies": ["onboarding"],
|
||||
"codeowners": ["@home-assistant/core"],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ DEFAULT_QOS = 0
|
||||
TRIGGER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
|
||||
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Required(CONF_TOPIC): mqtt.util.valid_subscribe_topic,
|
||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
||||
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
||||
vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All(
|
||||
|
24
homeassistant/components/avri/.translations/en.json
Normal file
24
homeassistant/components/avri/.translations/en.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This address is already configured."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Unknown 2 letter country code.",
|
||||
"invalid_house_number": "Invalid house number."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Letter country code",
|
||||
"house_number": "House number",
|
||||
"house_number_extension": "House number extension",
|
||||
"zip_code": "Zip code"
|
||||
},
|
||||
"description": "Enter your address",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/.translations/nl.json
Normal file
24
homeassistant/components/avri/.translations/nl.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Dit adres is reeds geconfigureerd."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Onbekende landcode",
|
||||
"invalid_house_number": "Ongeldig huisnummer."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Letter landcode",
|
||||
"house_number": "Huisnummer",
|
||||
"house_number_extension": "Huisnummer toevoeging",
|
||||
"zip_code": "Postcode"
|
||||
},
|
||||
"description": "Vul je adres in.",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
@@ -1 +1,63 @@
|
||||
"""The avri component."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from avri.api import Avri
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_HOUSE_NUMBER,
|
||||
CONF_HOUSE_NUMBER_EXTENSION,
|
||||
CONF_ZIP_CODE,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = ["sensor"]
|
||||
SCAN_INTERVAL = timedelta(hours=4)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""Set up the Avri component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Set up Avri from a config entry."""
|
||||
client = Avri(
|
||||
postal_code=entry.data[CONF_ZIP_CODE],
|
||||
house_nr=entry.data[CONF_HOUSE_NUMBER],
|
||||
house_nr_extension=entry.data.get(CONF_HOUSE_NUMBER_EXTENSION),
|
||||
country_code=entry.data[CONF_COUNTRY_CODE],
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = client
|
||||
|
||||
for component in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Unload a config entry."""
|
||||
unload_ok = all(
|
||||
await asyncio.gather(
|
||||
*[
|
||||
hass.config_entries.async_forward_entry_unload(entry, component)
|
||||
for component in PLATFORMS
|
||||
]
|
||||
)
|
||||
)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
74
homeassistant/components/avri/config_flow.py
Normal file
74
homeassistant/components/avri/config_flow.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Config flow for Avri component."""
|
||||
import pycountry
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_ID
|
||||
|
||||
from .const import (
|
||||
CONF_COUNTRY_CODE,
|
||||
CONF_HOUSE_NUMBER,
|
||||
CONF_HOUSE_NUMBER_EXTENSION,
|
||||
CONF_ZIP_CODE,
|
||||
DEFAULT_COUNTRY_CODE,
|
||||
)
|
||||
from .const import DOMAIN # pylint:disable=unused-import
|
||||
|
||||
DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ZIP_CODE): str,
|
||||
vol.Required(CONF_HOUSE_NUMBER): int,
|
||||
vol.Optional(CONF_HOUSE_NUMBER_EXTENSION): str,
|
||||
vol.Optional(CONF_COUNTRY_CODE, default=DEFAULT_COUNTRY_CODE): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AvriConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Avri config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def _show_setup_form(self, errors=None):
|
||||
"""Show the setup form to the user."""
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors or {},
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the initial step."""
|
||||
if user_input is None:
|
||||
return await self._show_setup_form()
|
||||
|
||||
zip_code = user_input[CONF_ZIP_CODE].replace(" ", "").upper()
|
||||
|
||||
errors = {}
|
||||
if user_input[CONF_HOUSE_NUMBER] <= 0:
|
||||
errors[CONF_HOUSE_NUMBER] = "invalid_house_number"
|
||||
return await self._show_setup_form(errors)
|
||||
if not pycountry.countries.get(alpha_2=user_input[CONF_COUNTRY_CODE]):
|
||||
errors[CONF_COUNTRY_CODE] = "invalid_country_code"
|
||||
return await self._show_setup_form(errors)
|
||||
|
||||
unique_id = (
|
||||
f"{zip_code}"
|
||||
f" "
|
||||
f"{user_input[CONF_HOUSE_NUMBER]}"
|
||||
f'{user_input.get(CONF_HOUSE_NUMBER_EXTENSION, "")}'
|
||||
)
|
||||
|
||||
await self.async_set_unique_id(unique_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=unique_id,
|
||||
data={
|
||||
CONF_ID: unique_id,
|
||||
CONF_ZIP_CODE: zip_code,
|
||||
CONF_HOUSE_NUMBER: user_input[CONF_HOUSE_NUMBER],
|
||||
CONF_HOUSE_NUMBER_EXTENSION: user_input.get(
|
||||
CONF_HOUSE_NUMBER_EXTENSION, ""
|
||||
),
|
||||
CONF_COUNTRY_CODE: user_input[CONF_COUNTRY_CODE],
|
||||
},
|
||||
)
|
8
homeassistant/components/avri/const.py
Normal file
8
homeassistant/components/avri/const.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Constants for the Avri integration."""
|
||||
CONF_COUNTRY_CODE = "country_code"
|
||||
CONF_ZIP_CODE = "zip_code"
|
||||
CONF_HOUSE_NUMBER = "house_number"
|
||||
CONF_HOUSE_NUMBER_EXTENSION = "house_number_extension"
|
||||
DOMAIN = "avri"
|
||||
ICON = "mdi:trash-can-outline"
|
||||
DEFAULT_COUNTRY_CODE = "NL"
|
@@ -2,6 +2,12 @@
|
||||
"domain": "avri",
|
||||
"name": "Avri",
|
||||
"documentation": "https://www.home-assistant.io/integrations/avri",
|
||||
"requirements": ["avri-api==0.1.7"],
|
||||
"codeowners": ["@timvancann"]
|
||||
}
|
||||
"requirements": [
|
||||
"avri-api==0.1.7",
|
||||
"pycountry==19.8.18"
|
||||
],
|
||||
"codeowners": [
|
||||
"@timvancann"
|
||||
],
|
||||
"config_flow": true
|
||||
}
|
@@ -1,45 +1,25 @@
|
||||
"""Support for Avri waste curbside collection pickup."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from avri.api import Avri, AvriException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ID, DEVICE_CLASS_TIMESTAMP
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
|
||||
from .const import DOMAIN, ICON
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_COUNTRY_CODE = "country_code"
|
||||
CONF_ZIP_CODE = "zip_code"
|
||||
CONF_HOUSE_NUMBER = "house_number"
|
||||
CONF_HOUSE_NUMBER_EXTENSION = "house_number_extension"
|
||||
DEFAULT_NAME = "avri"
|
||||
ICON = "mdi:trash-can-outline"
|
||||
SCAN_INTERVAL = timedelta(hours=4)
|
||||
DEFAULT_COUNTRY_CODE = "NL"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ZIP_CODE): cv.string,
|
||||
vol.Required(CONF_HOUSE_NUMBER): cv.positive_int,
|
||||
vol.Optional(CONF_HOUSE_NUMBER_EXTENSION): cv.string,
|
||||
vol.Optional(CONF_COUNTRY_CODE, default=DEFAULT_COUNTRY_CODE): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
|
||||
) -> None:
|
||||
"""Set up the Avri Waste platform."""
|
||||
client = Avri(
|
||||
postal_code=config[CONF_ZIP_CODE],
|
||||
house_nr=config[CONF_HOUSE_NUMBER],
|
||||
house_nr_extension=config.get(CONF_HOUSE_NUMBER_EXTENSION),
|
||||
country_code=config[CONF_COUNTRY_CODE],
|
||||
)
|
||||
client = hass.data[DOMAIN][entry.entry_id]
|
||||
integration_id = entry.data[CONF_ID]
|
||||
|
||||
try:
|
||||
each_upcoming = client.upcoming_of_each()
|
||||
@@ -47,22 +27,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
raise PlatformNotReady from ex
|
||||
else:
|
||||
entities = [
|
||||
AvriWasteUpcoming(config[CONF_NAME], client, upcoming.name)
|
||||
AvriWasteUpcoming(client, upcoming.name, integration_id)
|
||||
for upcoming in each_upcoming
|
||||
]
|
||||
add_entities(entities, True)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class AvriWasteUpcoming(Entity):
|
||||
"""Avri Waste Sensor."""
|
||||
|
||||
def __init__(self, name: str, client: Avri, waste_type: str):
|
||||
def __init__(self, client: Avri, waste_type: str, integration_id: str):
|
||||
"""Initialize the sensor."""
|
||||
self._waste_type = waste_type
|
||||
self._name = f"{name}_{self._waste_type}"
|
||||
self._name = f"{self._waste_type}".title()
|
||||
self._state = None
|
||||
self._client = client
|
||||
self._state_available = False
|
||||
self._integration_id = integration_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -72,13 +53,7 @@ class AvriWasteUpcoming(Entity):
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique ID."""
|
||||
return (
|
||||
f"{self._waste_type}"
|
||||
f"-{self._client.country_code}"
|
||||
f"-{self._client.postal_code}"
|
||||
f"-{self._client.house_nr}"
|
||||
f"-{self._client.house_nr_extension}"
|
||||
)
|
||||
return (f"{self._integration_id}" f"-{self._waste_type}").replace(" ", "")
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@@ -90,13 +65,21 @@ class AvriWasteUpcoming(Entity):
|
||||
"""Return True if entity is available."""
|
||||
return self._state_available
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the device class of the sensor."""
|
||||
return DEVICE_CLASS_TIMESTAMP
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to use in the frontend."""
|
||||
return ICON
|
||||
|
||||
def update(self):
|
||||
"""Update device state."""
|
||||
async def async_update(self):
|
||||
"""Update the data."""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
pickup_events = self._client.upcoming_of_each()
|
||||
except AvriException as ex:
|
||||
|
24
homeassistant/components/avri/strings.json
Normal file
24
homeassistant/components/avri/strings.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"title": "Avri",
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This address is already configured."
|
||||
},
|
||||
"error": {
|
||||
"invalid_house_number": "Invalid house number.",
|
||||
"invalid_country_code": "Unknown 2 letter country code."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"zip_code": "Zip code",
|
||||
"house_number": "House number",
|
||||
"house_number_extension": "House number extension",
|
||||
"country_code": "2 Letter country code"
|
||||
},
|
||||
"description": "Enter your address",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
7
homeassistant/components/avri/translations/ar.json
Normal file
7
homeassistant/components/avri/translations/ar.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u062a\u0645 \u062a\u0643\u0648\u064a\u0646 \u0647\u0630\u0627 \u0627\u0644\u0639\u0646\u0648\u0627\u0646 \u0628\u0627\u0644\u0641\u0639\u0644."
|
||||
}
|
||||
}
|
||||
}
|
24
homeassistant/components/avri/translations/ca.json
Normal file
24
homeassistant/components/avri/translations/ca.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Aquesta adre\u00e7a ja est\u00e0 configurada."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Codi de pa\u00eds desconegut.",
|
||||
"invalid_house_number": "N\u00famero de casa no v\u00e0lid."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "Codi de pa\u00eds de 2 lletres",
|
||||
"house_number": "N\u00famero de casa",
|
||||
"house_number_extension": "Ampliaci\u00f3 de n\u00famero de casa",
|
||||
"zip_code": "Codi postal"
|
||||
},
|
||||
"description": "Introdueix la teva adre\u00e7a",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
18
homeassistant/components/avri/translations/de.json
Normal file
18
homeassistant/components/avri/translations/de.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"invalid_house_number": "Ung\u00fcltige Hausnummer"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"house_number": "Hausnummer",
|
||||
"zip_code": "Postleitzahl"
|
||||
},
|
||||
"description": "Gibt deine Adresse ein",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/en.json
Normal file
24
homeassistant/components/avri/translations/en.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "This address is already configured."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Unknown 2 letter country code.",
|
||||
"invalid_house_number": "Invalid house number."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Letter country code",
|
||||
"house_number": "House number",
|
||||
"house_number_extension": "House number extension",
|
||||
"zip_code": "Zip code"
|
||||
},
|
||||
"description": "Enter your address",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/es.json
Normal file
24
homeassistant/components/avri/translations/es.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Esta direcci\u00f3n ya est\u00e1 configurada."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "C\u00f3digo de pa\u00eds de 2 letras desconocido.",
|
||||
"invalid_house_number": "N\u00famero de casa no v\u00e1lido."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "C\u00f3digo de pa\u00eds de 2 letras",
|
||||
"house_number": "N\u00famero de casa",
|
||||
"house_number_extension": "Extensi\u00f3n del n\u00famero de casa",
|
||||
"zip_code": "C\u00f3digo postal"
|
||||
},
|
||||
"description": "Introduce tu direccion",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/fr.json
Normal file
24
homeassistant/components/avri/translations/fr.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Cette adresse est d\u00e9j\u00e0 configur\u00e9e."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Code pays \u00e0 2 lettres inconnu.",
|
||||
"invalid_house_number": "Num\u00e9ro de maison invalide."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "Code pays \u00e0 2 lettres",
|
||||
"house_number": "Num\u00e9ro de maison",
|
||||
"house_number_extension": "Extension de num\u00e9ro de maison",
|
||||
"zip_code": "Code postal"
|
||||
},
|
||||
"description": "Entrez votre adresse",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/it.json
Normal file
24
homeassistant/components/avri/translations/it.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Questo indirizzo \u00e8 gi\u00e0 configurato."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Codice paese di 2 lettere sconosciuto.",
|
||||
"invalid_house_number": "Numero civico non valido."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "Codice paese di 2 lettere",
|
||||
"house_number": "Numero civico",
|
||||
"house_number_extension": "Estensione del numero civico",
|
||||
"zip_code": "Codice di avviamento postale"
|
||||
},
|
||||
"description": "Inserisci il tuo indirizzo",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/ko.json
Normal file
24
homeassistant/components/avri/translations/ko.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\uc774 \uc8fc\uc18c\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "\uc54c \uc218 \uc5c6\ub294 \uad6d\uac00\ucf54\ub4dc\uc785\ub2c8\ub2e4.",
|
||||
"invalid_house_number": "\uc9d1 \ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 \ubb38\uc790 \uad6d\uac00\ucf54\ub4dc",
|
||||
"house_number": "\uc9d1 \ubc88\ud638",
|
||||
"house_number_extension": "\uc9d1 \ubc88\ud638 \ucd94\uac00\uc815\ubcf4",
|
||||
"zip_code": "\uc6b0\ud3b8 \ubc88\ud638"
|
||||
},
|
||||
"description": "\uc8fc\uc18c\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
22
homeassistant/components/avri/translations/lb.json
Normal file
22
homeassistant/components/avri/translations/lb.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "D\u00ebs Adress ass scho konfigur\u00e9iert."
|
||||
},
|
||||
"error": {
|
||||
"invalid_house_number": "Ong\u00eblteg Haus Nummer"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"house_number": "Haus Nummer",
|
||||
"house_number_extension": "Haus Nummer Extensioun",
|
||||
"zip_code": "Postleitzuel"
|
||||
},
|
||||
"description": "G\u00ebff deng Adresse un",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/no.json
Normal file
24
homeassistant/components/avri/translations/no.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Denne adressen er allerede konfigurert."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "Ukjent landskode p\u00e5 2 bokstaver.",
|
||||
"invalid_house_number": "Ugyldig husnummer."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "2 Bokstavs landskode",
|
||||
"house_number": "Husnummer",
|
||||
"house_number_extension": "Utvidelse av husnummer",
|
||||
"zip_code": "Postnummer"
|
||||
},
|
||||
"description": "Skriv inn adressen din",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
24
homeassistant/components/avri/translations/ru.json
Normal file
24
homeassistant/components/avri/translations/ru.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430."
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0434\u0432\u0443\u0445\u0431\u0443\u043a\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b.",
|
||||
"invalid_house_number": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0434\u043e\u043c\u0430."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "\u0414\u0432\u0443\u0445\u0431\u0443\u043a\u0432\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441\u0442\u0440\u0430\u043d\u044b",
|
||||
"house_number": "\u041d\u043e\u043c\u0435\u0440 \u0434\u043e\u043c\u0430",
|
||||
"house_number_extension": "\u041b\u0438\u0442\u0435\u0440 \u0434\u043e\u043c\u0430 / \u0434\u043e\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435",
|
||||
"zip_code": "\u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441"
|
||||
},
|
||||
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Avri.",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
24
homeassistant/components/avri/translations/zh-Hant.json
Normal file
24
homeassistant/components/avri/translations/zh-Hant.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "\u6b64\u5730\u5740\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002"
|
||||
},
|
||||
"error": {
|
||||
"invalid_country_code": "\u672a\u77e5\u570b\u78bc\uff08\u5169\u5b57\u6bcd\uff09\u3002",
|
||||
"invalid_house_number": "\u9580\u724c\u865f\u78bc\u932f\u8aa4\u3002"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"country_code": "\u570b\u78bc\uff08\u5169\u5b57\u6bcd\uff09",
|
||||
"house_number": "\u9580\u724c\u865f\u78bc",
|
||||
"house_number_extension": "\u9580\u724c\u865f\u78bc\u5206\u865f",
|
||||
"zip_code": "\u90f5\u905e\u5340\u865f"
|
||||
},
|
||||
"description": "\u8f38\u5165\u5730\u5740",
|
||||
"title": "Avri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Avri"
|
||||
}
|
@@ -1 +1,112 @@
|
||||
"""The awair component."""
|
||||
|
||||
from asyncio import gather
|
||||
from typing import Any, Optional
|
||||
|
||||
from async_timeout import timeout
|
||||
from python_awair import Awair
|
||||
from python_awair.exceptions import AuthError
|
||||
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
from homeassistant.core import Config, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import API_TIMEOUT, DOMAIN, LOGGER, UPDATE_INTERVAL, AwairResult
|
||||
|
||||
PLATFORMS = ["sensor"]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: Config) -> bool:
|
||||
"""Set up Awair integration."""
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry) -> bool:
|
||||
"""Set up Awair integration from a config entry."""
|
||||
session = async_get_clientsession(hass)
|
||||
coordinator = AwairDataUpdateCoordinator(hass, config_entry, session)
|
||||
|
||||
await coordinator.async_refresh()
|
||||
|
||||
if not coordinator.last_update_success:
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = coordinator
|
||||
|
||||
for platform in PLATFORMS:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(config_entry, platform)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry) -> bool:
|
||||
"""Unload Awair configuration."""
|
||||
tasks = []
|
||||
for platform in PLATFORMS:
|
||||
tasks.append(
|
||||
hass.config_entries.async_forward_entry_unload(config_entry, platform)
|
||||
)
|
||||
|
||||
unload_ok = all(await gather(*tasks))
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class AwairDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Define a wrapper class to update Awair data."""
|
||||
|
||||
def __init__(self, hass, config_entry, session) -> None:
|
||||
"""Set up the AwairDataUpdateCoordinator class."""
|
||||
access_token = config_entry.data[CONF_ACCESS_TOKEN]
|
||||
self._awair = Awair(access_token=access_token, session=session)
|
||||
self._config_entry = config_entry
|
||||
|
||||
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL)
|
||||
|
||||
async def _async_update_data(self) -> Optional[Any]:
|
||||
"""Update data via Awair client library."""
|
||||
with timeout(API_TIMEOUT):
|
||||
try:
|
||||
LOGGER.debug("Fetching users and devices")
|
||||
user = await self._awair.user()
|
||||
devices = await user.devices()
|
||||
results = await gather(
|
||||
*[self._fetch_air_data(device) for device in devices]
|
||||
)
|
||||
return {result.device.uuid: result for result in results}
|
||||
except AuthError as err:
|
||||
flow_context = {
|
||||
"source": "reauth",
|
||||
"unique_id": self._config_entry.unique_id,
|
||||
}
|
||||
|
||||
matching_flows = [
|
||||
flow
|
||||
for flow in self.hass.config_entries.flow.async_progress()
|
||||
if flow["context"] == flow_context
|
||||
]
|
||||
|
||||
if not matching_flows:
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.flow.async_init(
|
||||
DOMAIN, context=flow_context, data=self._config_entry.data,
|
||||
)
|
||||
)
|
||||
|
||||
raise UpdateFailed(err)
|
||||
except Exception as err:
|
||||
raise UpdateFailed(err)
|
||||
|
||||
async def _fetch_air_data(self, device):
|
||||
"""Fetch latest air quality data."""
|
||||
LOGGER.debug("Fetching data for %s", device.uuid)
|
||||
air_data = await device.air_data_latest()
|
||||
LOGGER.debug(air_data)
|
||||
return AwairResult(device=device, air_data=air_data)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user