mirror of
https://github.com/home-assistant/core.git
synced 2026-01-09 17:17:17 +01:00
Compare commits
489 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0200cdbfe | ||
|
|
c9f64af85a | ||
|
|
e4d45bf53a | ||
|
|
e984868762 | ||
|
|
e5835eb7c8 | ||
|
|
f73cb0eba5 | ||
|
|
d3e011ff50 | ||
|
|
4255f2c62f | ||
|
|
b669e1498a | ||
|
|
c0fd22c285 | ||
|
|
785b42ecde | ||
|
|
8988ee5b34 | ||
|
|
61b2f1bff0 | ||
|
|
224c258876 | ||
|
|
e4d76d5c44 | ||
|
|
c702e1e3c6 | ||
|
|
47660f9312 | ||
|
|
1a5028f56f | ||
|
|
ca729b178b | ||
|
|
557b745053 | ||
|
|
0400e29f7a | ||
|
|
8db2152230 | ||
|
|
47848e26f9 | ||
|
|
c7ff8d4996 | ||
|
|
69ec7980ad | ||
|
|
5a0c707a37 | ||
|
|
473bf93973 | ||
|
|
ed75549123 | ||
|
|
3e98aad8a2 | ||
|
|
542160fc56 | ||
|
|
e2cc1564a0 | ||
|
|
2836ff86fe | ||
|
|
cb07ea0d60 | ||
|
|
91aa874c0c | ||
|
|
ca0ee509e7 | ||
|
|
8062f48973 | ||
|
|
ed299a9137 | ||
|
|
349de19316 | ||
|
|
7c9597f824 | ||
|
|
a011048a4e | ||
|
|
0ef9882e2e | ||
|
|
ec4495bd0c | ||
|
|
c5c64e738e | ||
|
|
89fc3b2a1b | ||
|
|
48f0e8311b | ||
|
|
e0e5b860e4 | ||
|
|
988bcf9399 | ||
|
|
e95c50c742 | ||
|
|
6ff4ea1126 | ||
|
|
a7c74151bc | ||
|
|
6859d5216e | ||
|
|
73a0c664b8 | ||
|
|
b0ff51b0ef | ||
|
|
e22802a4d4 | ||
|
|
aa29eeba04 | ||
|
|
cc74035c3b | ||
|
|
fe47253f68 | ||
|
|
3ee3acd550 | ||
|
|
234c759b45 | ||
|
|
ba4d4bcd29 | ||
|
|
d7bbdb033d | ||
|
|
63fd5f2d31 | ||
|
|
f353d51ab1 | ||
|
|
3f484228cb | ||
|
|
717a0c2b2d | ||
|
|
34090bd021 | ||
|
|
bb1583c453 | ||
|
|
d7ba2aad1d | ||
|
|
bb4ca1f525 | ||
|
|
bd335e1ac1 | ||
|
|
2c7060896b | ||
|
|
abeb875c61 | ||
|
|
41c1997b88 | ||
|
|
995758b8ac | ||
|
|
f33e432cab | ||
|
|
29984efd8c | ||
|
|
d179686edf | ||
|
|
0c87fb421e | ||
|
|
f575d1d3a6 | ||
|
|
3d4f2926e9 | ||
|
|
7412d0f97c | ||
|
|
f91ff76b95 | ||
|
|
648adcc708 | ||
|
|
8804f55fcc | ||
|
|
1f93984fd5 | ||
|
|
ae84a91ea8 | ||
|
|
2aab646be2 | ||
|
|
10e3698fd7 | ||
|
|
7368c623d4 | ||
|
|
d82e5ecbb0 | ||
|
|
239c60c09f | ||
|
|
38c33bd01e | ||
|
|
86f5f0226c | ||
|
|
c78d3b6154 | ||
|
|
dcfe7b2280 | ||
|
|
a368db9ad4 | ||
|
|
6330bb1004 | ||
|
|
672a896124 | ||
|
|
87316c4e83 | ||
|
|
38b1ce3fe0 | ||
|
|
9920699bb8 | ||
|
|
b9bf6963fd | ||
|
|
60dc337f3f | ||
|
|
85c72fbca6 | ||
|
|
85ccd71d39 | ||
|
|
09cbcb74bc | ||
|
|
0e453fe492 | ||
|
|
1d16bb2cd4 | ||
|
|
05d41bc0ee | ||
|
|
f3285f96bb | ||
|
|
1d5ffe9ad5 | ||
|
|
ed6e349515 | ||
|
|
a85e018bc4 | ||
|
|
a0b93c2add | ||
|
|
e593383b4d | ||
|
|
b3c3721a79 | ||
|
|
310c073c64 | ||
|
|
d39784906b | ||
|
|
6d2e7db123 | ||
|
|
d8e43978b7 | ||
|
|
76c0295403 | ||
|
|
2bc7444427 | ||
|
|
4518e6bdf7 | ||
|
|
d6c12e47f4 | ||
|
|
cea2bf94bd | ||
|
|
1fcaaf93ad | ||
|
|
d4c7515681 | ||
|
|
c94834d8f6 | ||
|
|
ec5da05804 | ||
|
|
d84cd01cbf | ||
|
|
a1da6a677a | ||
|
|
55943cfac0 | ||
|
|
567e3e6e50 | ||
|
|
259915eee9 | ||
|
|
d7859b5900 | ||
|
|
ebcae2503c | ||
|
|
400aaf8a3a | ||
|
|
046683ee3f | ||
|
|
c7f5beb794 | ||
|
|
c508ba166c | ||
|
|
68bd5f5df8 | ||
|
|
70c5807976 | ||
|
|
3b1534c126 | ||
|
|
2559bc4226 | ||
|
|
0be922dc9c | ||
|
|
7038dd484a | ||
|
|
1bd31e3459 | ||
|
|
77ee2f1f3e | ||
|
|
761385dea1 | ||
|
|
2b2809a4c6 | ||
|
|
3c32bfda95 | ||
|
|
65d9460e09 | ||
|
|
2b542b7789 | ||
|
|
bbdb7a6f4c | ||
|
|
074fcd96ed | ||
|
|
5580bec1d3 | ||
|
|
af3afb673a | ||
|
|
697c331903 | ||
|
|
971d933140 | ||
|
|
0300ef2040 | ||
|
|
e049b35413 | ||
|
|
a396ee2cb5 | ||
|
|
7ca7951526 | ||
|
|
5bf3b2dd9f | ||
|
|
cdcc535ae1 | ||
|
|
4662ab215c | ||
|
|
80aa2075c6 | ||
|
|
4b7d944a74 | ||
|
|
b7218e6a1d | ||
|
|
c6cee1ccd3 | ||
|
|
16a4180fab | ||
|
|
e8f0e534f9 | ||
|
|
07f1e2ce75 | ||
|
|
eaa9c4d437 | ||
|
|
db277ad023 | ||
|
|
3484e506e8 | ||
|
|
e964750ac1 | ||
|
|
c87c5797db | ||
|
|
5b25188474 | ||
|
|
91ef78adc5 | ||
|
|
e2a4fdeadf | ||
|
|
9b7780edf0 | ||
|
|
f84c0ee473 | ||
|
|
89ba374d51 | ||
|
|
a8ef7a2774 | ||
|
|
5a30b0507d | ||
|
|
6d0ac30687 | ||
|
|
d419471372 | ||
|
|
0ceace96e7 | ||
|
|
3e056a24dd | ||
|
|
ec7f2657cd | ||
|
|
5945929e7e | ||
|
|
6511e11ec9 | ||
|
|
4b3cdb9f4e | ||
|
|
bb21cb6c89 | ||
|
|
9667c8057f | ||
|
|
028cc8d24f | ||
|
|
c36c708068 | ||
|
|
df47a8c58c | ||
|
|
6ca0da5c52 | ||
|
|
e4f42d1282 | ||
|
|
ec9575a86f | ||
|
|
5c208da82e | ||
|
|
9482a6303d | ||
|
|
657544a381 | ||
|
|
5999df1953 | ||
|
|
143eb4e8f4 | ||
|
|
41d2321756 | ||
|
|
f02e887fcc | ||
|
|
97e8e20bcc | ||
|
|
33ed113211 | ||
|
|
ba2b28cd4d | ||
|
|
d843bf9c58 | ||
|
|
84d6453a97 | ||
|
|
1f54edfbc4 | ||
|
|
27be95e597 | ||
|
|
935e5c67a3 | ||
|
|
3fcbcd5a38 | ||
|
|
dbba3eb0d4 | ||
|
|
89e9d827a2 | ||
|
|
ab4e4787e3 | ||
|
|
b6e1675c46 | ||
|
|
e69ca810e4 | ||
|
|
62844e237c | ||
|
|
1218127d83 | ||
|
|
08a57959b9 | ||
|
|
d2dec44b18 | ||
|
|
f771667c14 | ||
|
|
d5dcb8f140 | ||
|
|
362ac725bf | ||
|
|
58bb6f2e99 | ||
|
|
5b8cb10ad7 | ||
|
|
2eb5ce9dfe | ||
|
|
fd2cff6b1c | ||
|
|
0e5fa010a7 | ||
|
|
7c25389f0d | ||
|
|
a8d3a904e7 | ||
|
|
6bf42ad43d | ||
|
|
0987219b28 | ||
|
|
fb52f66da0 | ||
|
|
8000b97180 | ||
|
|
5b8f64093b | ||
|
|
440d479be8 | ||
|
|
e80702a45c | ||
|
|
63b19094c1 | ||
|
|
84b1fcbc36 | ||
|
|
81a5208762 | ||
|
|
afa019ae47 | ||
|
|
6800871c13 | ||
|
|
f094a7369d | ||
|
|
234f348ba1 | ||
|
|
d1c6eb4f3e | ||
|
|
5232df34cb | ||
|
|
0fe5d567a2 | ||
|
|
136364f5db | ||
|
|
2de6a94506 | ||
|
|
e1b63d9706 | ||
|
|
27a8171a8b | ||
|
|
722d285904 | ||
|
|
bc8aa73448 | ||
|
|
b557157ea1 | ||
|
|
85404783d6 | ||
|
|
06440bf076 | ||
|
|
84a2e5d8fb | ||
|
|
9bb7e40ee3 | ||
|
|
368682647d | ||
|
|
1d86905d5b | ||
|
|
8748ace244 | ||
|
|
5e73846bcc | ||
|
|
b5bfc759ec | ||
|
|
075b575bde | ||
|
|
29c6584fe2 | ||
|
|
48127cade0 | ||
|
|
c8efbb2cdc | ||
|
|
9d112dc3f0 | ||
|
|
11c78d5de8 | ||
|
|
bc30491dc0 | ||
|
|
fe93ea9bdf | ||
|
|
75fa9b2fba | ||
|
|
78da6828f0 | ||
|
|
19e19009cc | ||
|
|
1e784b4d7a | ||
|
|
c218757336 | ||
|
|
25f6302813 | ||
|
|
4b3d4b275e | ||
|
|
f36755e447 | ||
|
|
9fd21d20ae | ||
|
|
cd6679eb5b | ||
|
|
1b79872dd6 | ||
|
|
732743aeb5 | ||
|
|
0ec1401be7 | ||
|
|
cc166bf6a7 | ||
|
|
11602c1da0 | ||
|
|
a3f0d55737 | ||
|
|
336b6adc88 | ||
|
|
5b53bd6aa0 | ||
|
|
5fd1053a38 | ||
|
|
80bc42af4f | ||
|
|
c8d885fb78 | ||
|
|
e73569c203 | ||
|
|
0f3b6f1739 | ||
|
|
af2949f85f | ||
|
|
b3886820b4 | ||
|
|
d717d9f6be | ||
|
|
f225570980 | ||
|
|
e505a9b7b4 | ||
|
|
ef79566864 | ||
|
|
fff3cb0b46 | ||
|
|
5652a4a58b | ||
|
|
cb9e0c03d5 | ||
|
|
d6d28dd3e9 | ||
|
|
eb610e6093 | ||
|
|
7db28d3d91 | ||
|
|
452d7cfd61 | ||
|
|
7f3871028d | ||
|
|
e476949c3e | ||
|
|
9036aafc81 | ||
|
|
2a2318b7f6 | ||
|
|
b75356d532 | ||
|
|
0f92d061c4 | ||
|
|
3b83a64f7c | ||
|
|
db87842335 | ||
|
|
798f630029 | ||
|
|
96b8c517f0 | ||
|
|
4af4b2d10e | ||
|
|
2339cb05ad | ||
|
|
aae6ff830a | ||
|
|
1c11394f5f | ||
|
|
162e2b8385 | ||
|
|
e295ca7b8e | ||
|
|
3e325a4ef9 | ||
|
|
0007f35f96 | ||
|
|
4e020b90e1 | ||
|
|
04636e9ba7 | ||
|
|
218c82eaf3 | ||
|
|
22c0733d8e | ||
|
|
d3f2854c89 | ||
|
|
eabc7b22cd | ||
|
|
012e91f9b1 | ||
|
|
6395087a40 | ||
|
|
b3580f46b9 | ||
|
|
3bdee57066 | ||
|
|
2208563de4 | ||
|
|
418fa226e6 | ||
|
|
ba21608042 | ||
|
|
7676b3fbe8 | ||
|
|
5ab3c7b765 | ||
|
|
6cba51fd0e | ||
|
|
fe148606b8 | ||
|
|
574669bd20 | ||
|
|
83c5dc67f7 | ||
|
|
2ffadde0a3 | ||
|
|
7dac7b9e5e | ||
|
|
d820efc4e3 | ||
|
|
8755389c49 | ||
|
|
11647f9fab | ||
|
|
578bfe9798 | ||
|
|
8c27bf8c7c | ||
|
|
ab4e1fddd5 | ||
|
|
b9a488912a | ||
|
|
199db7219e | ||
|
|
937688f7a6 | ||
|
|
25408bd483 | ||
|
|
a65d14c0cd | ||
|
|
12d16d9bdc | ||
|
|
14dd8791ec | ||
|
|
088584b66d | ||
|
|
963ffa1ccc | ||
|
|
990e7c57f2 | ||
|
|
0979ce476a | ||
|
|
ded37d971d | ||
|
|
7a83b86ebd | ||
|
|
8ef2f1f67b | ||
|
|
99c2e4ac44 | ||
|
|
fe92cf1e72 | ||
|
|
7dbbea2238 | ||
|
|
7be015fcc6 | ||
|
|
a8f22287ca | ||
|
|
b3a08d5876 | ||
|
|
e2f55a959f | ||
|
|
b81260e912 | ||
|
|
7be197b845 | ||
|
|
fd21d6cc9d | ||
|
|
3f65a03024 | ||
|
|
82c6d3d8c2 | ||
|
|
71eaef8da4 | ||
|
|
d812f23f6b | ||
|
|
17dce6697f | ||
|
|
49cfebd903 | ||
|
|
2228f2ef66 | ||
|
|
734d8c52e9 | ||
|
|
caf0751be8 | ||
|
|
7b81727c69 | ||
|
|
97394df0b9 | ||
|
|
c3e9bd1444 | ||
|
|
31d92683f7 | ||
|
|
cee51ecb2b | ||
|
|
646aaab936 | ||
|
|
4c1eeb9e96 | ||
|
|
ca460ace5d | ||
|
|
2be0d1b096 | ||
|
|
47f64b472d | ||
|
|
96d20a64d5 | ||
|
|
4d187e08d4 | ||
|
|
4d52adb008 | ||
|
|
6c29315088 | ||
|
|
6403a13ea3 | ||
|
|
de76b59d0b | ||
|
|
616f23ae1d | ||
|
|
d859c3fa86 | ||
|
|
e753ffca94 | ||
|
|
c44f5d31ef | ||
|
|
e6a2c18430 | ||
|
|
8b49ecbe7d | ||
|
|
01eee52990 | ||
|
|
9aed40a88d | ||
|
|
5cab319798 | ||
|
|
4394e37df9 | ||
|
|
64b4c8f43a | ||
|
|
a3d05328ec | ||
|
|
2bdbf6955d | ||
|
|
23382ab199 | ||
|
|
6d9fda04ac | ||
|
|
b4c657a39c | ||
|
|
35cb0458fa | ||
|
|
6d3343e4d1 | ||
|
|
f73bda1218 | ||
|
|
c29bffc8d8 | ||
|
|
cc6e70a270 | ||
|
|
42821b5f64 | ||
|
|
c164533404 | ||
|
|
acdf9c7ce2 | ||
|
|
0cea54cea1 | ||
|
|
203701bc7c | ||
|
|
44f6151548 | ||
|
|
1a5fe3d880 | ||
|
|
a62e514d8f | ||
|
|
f0f386e314 | ||
|
|
bb37cf906c | ||
|
|
406b45c6e7 | ||
|
|
377b129c9c | ||
|
|
410f19c777 | ||
|
|
4bbfc04f5e | ||
|
|
c7a32e59b7 | ||
|
|
fb9aad8791 | ||
|
|
493d2743ba | ||
|
|
f259c5724b | ||
|
|
ea8bb28d21 | ||
|
|
8aa136f7ed | ||
|
|
4905f4dd97 | ||
|
|
45fae5a50e | ||
|
|
2eec2cc656 | ||
|
|
a57aae9891 | ||
|
|
9cdfa77a21 | ||
|
|
0af635e8d7 | ||
|
|
08ac6da8a6 | ||
|
|
8701be095b | ||
|
|
0b57cfb004 | ||
|
|
ddeb7f3bea | ||
|
|
44c619a853 | ||
|
|
d8370f44cb | ||
|
|
dd75c49796 | ||
|
|
8b232e7ce6 | ||
|
|
3c465434cd | ||
|
|
e30c324b32 | ||
|
|
903c86a116 | ||
|
|
c96778c82a | ||
|
|
1e18a2c679 | ||
|
|
5b35317e1e | ||
|
|
bf4830bc07 | ||
|
|
3ffa0176cc | ||
|
|
ccbc231d3a | ||
|
|
dee229152f | ||
|
|
76c30aca38 | ||
|
|
3d0c3ab746 | ||
|
|
32faf5b709 | ||
|
|
09ff272290 | ||
|
|
3a5ba77e04 | ||
|
|
68723730a7 | ||
|
|
0125b3fd80 | ||
|
|
fb5b5223fb | ||
|
|
aacf7ba9aa | ||
|
|
bf29824dac | ||
|
|
a1cb4018a1 | ||
|
|
c7700ad11c | ||
|
|
ed8f89df74 | ||
|
|
65c7bdc1ad | ||
|
|
bf40bea965 | ||
|
|
ef180c489a |
71
.coveragerc
71
.coveragerc
@@ -19,6 +19,9 @@ omit =
|
||||
homeassistant/components/alarmdecoder.py
|
||||
homeassistant/components/*/alarmdecoder.py
|
||||
|
||||
homeassistant/components/ambient_station/__init__.py
|
||||
homeassistant/components/ambient_station/sensor.py
|
||||
|
||||
homeassistant/components/amcrest.py
|
||||
homeassistant/components/*/amcrest.py
|
||||
|
||||
@@ -80,17 +83,24 @@ omit =
|
||||
homeassistant/components/digital_ocean.py
|
||||
homeassistant/components/*/digital_ocean.py
|
||||
|
||||
homeassistant/components/danfoss_air/*
|
||||
|
||||
homeassistant/components/dominos.py
|
||||
|
||||
homeassistant/components/doorbird.py
|
||||
homeassistant/components/*/doorbird.py
|
||||
|
||||
homeassistant/components/dovado/*
|
||||
|
||||
homeassistant/components/dweet.py
|
||||
homeassistant/components/*/dweet.py
|
||||
|
||||
homeassistant/components/eight_sleep.py
|
||||
homeassistant/components/*/eight_sleep.py
|
||||
|
||||
homeassistant/components/ecoal_boiler.py
|
||||
homeassistant/components/*/ecoal_boiler.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
homeassistant/components/*/ecobee.py
|
||||
|
||||
@@ -122,12 +132,17 @@ omit =
|
||||
homeassistant/components/*/ecovacs.py
|
||||
|
||||
homeassistant/components/esphome/__init__.py
|
||||
homeassistant/components/*/esphome.py
|
||||
homeassistant/components/esphome/binary_sensor.py
|
||||
homeassistant/components/esphome/cover.py
|
||||
homeassistant/components/esphome/fan.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/esphome/sensor.py
|
||||
homeassistant/components/esphome/switch.py
|
||||
|
||||
homeassistant/components/eufy.py
|
||||
homeassistant/components/*/eufy.py
|
||||
|
||||
homeassistant/components/fibaro.py
|
||||
homeassistant/components/fibaro/__init__.py
|
||||
homeassistant/components/*/fibaro.py
|
||||
|
||||
homeassistant/components/gc100.py
|
||||
@@ -158,13 +173,13 @@ omit =
|
||||
homeassistant/components/hlk_sw16.py
|
||||
homeassistant/components/*/hlk_sw16.py
|
||||
|
||||
homeassistant/components/homekit_controller/__init__.py
|
||||
homeassistant/components/*/homekit_controller.py
|
||||
homeassistant/components/homekit_controller/*
|
||||
|
||||
homeassistant/components/homematic/__init__.py
|
||||
homeassistant/components/*/homematic.py
|
||||
|
||||
homeassistant/components/homematicip_cloud.py
|
||||
homeassistant/components/homematicip_cloud/hap.py
|
||||
homeassistant/components/homematicip_cloud/device.py
|
||||
homeassistant/components/*/homematicip_cloud.py
|
||||
|
||||
homeassistant/components/homeworks.py
|
||||
@@ -234,7 +249,7 @@ omit =
|
||||
homeassistant/components/lutron_caseta.py
|
||||
homeassistant/components/*/lutron_caseta.py
|
||||
|
||||
homeassistant/components/*/mailgun.py
|
||||
homeassistant/components/mailgun/notify.py
|
||||
|
||||
homeassistant/components/matrix.py
|
||||
homeassistant/components/*/matrix.py
|
||||
@@ -276,7 +291,8 @@ omit =
|
||||
homeassistant/components/*/opentherm_gw.py
|
||||
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/*/openuv.py
|
||||
homeassistant/components/openuv/binary_sensor.py
|
||||
homeassistant/components/openuv/sensor.py
|
||||
|
||||
homeassistant/components/plum_lightpad.py
|
||||
homeassistant/components/*/plum_lightpad.py
|
||||
@@ -298,7 +314,9 @@ omit =
|
||||
homeassistant/components/*/raincloud.py
|
||||
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/*/rainmachine.py
|
||||
homeassistant/components/rainmachine/binary_sensor.py
|
||||
homeassistant/components/rainmachine/sensor.py
|
||||
homeassistant/components/rainmachine/switch.py
|
||||
|
||||
homeassistant/components/raspihats.py
|
||||
homeassistant/components/*/raspihats.py
|
||||
@@ -308,6 +326,9 @@ omit =
|
||||
homeassistant/components/rfxtrx.py
|
||||
homeassistant/components/*/rfxtrx.py
|
||||
|
||||
homeassistant/components/roku.py
|
||||
homeassistant/components/*/roku.py
|
||||
|
||||
homeassistant/components/rpi_gpio.py
|
||||
homeassistant/components/*/rpi_gpio.py
|
||||
|
||||
@@ -327,7 +348,7 @@ omit =
|
||||
homeassistant/components/*/sense.py
|
||||
|
||||
homeassistant/components/simplisafe/__init__.py
|
||||
homeassistant/components/*/simplisafe.py
|
||||
homeassistant/components/simplisafe/alarm_control_panel.py
|
||||
|
||||
homeassistant/components/sisyphus.py
|
||||
homeassistant/components/*/sisyphus.py
|
||||
@@ -373,6 +394,9 @@ omit =
|
||||
|
||||
homeassistant/components/tradfri.py
|
||||
homeassistant/components/*/tradfri.py
|
||||
|
||||
homeassistant/components/transmission.py
|
||||
homeassistant/components/*/transmission.py
|
||||
|
||||
homeassistant/components/notify/twilio_sms.py
|
||||
homeassistant/components/notify/twilio_call.py
|
||||
@@ -424,17 +448,27 @@ omit =
|
||||
homeassistant/components/*/zabbix.py
|
||||
|
||||
homeassistant/components/zha/__init__.py
|
||||
homeassistant/components/zha/binary_sensor.py
|
||||
homeassistant/components/zha/const.py
|
||||
homeassistant/components/zha/event.py
|
||||
homeassistant/components/zha/entities/*
|
||||
homeassistant/components/zha/helpers.py
|
||||
homeassistant/components/zha/fan.py
|
||||
homeassistant/components/zha/light.py
|
||||
homeassistant/components/zha/sensor.py
|
||||
homeassistant/components/zha/switch.py
|
||||
homeassistant/components/zha/api.py
|
||||
homeassistant/components/zha/entity.py
|
||||
homeassistant/components/zha/device_entity.py
|
||||
homeassistant/components/zha/core/helpers.py
|
||||
homeassistant/components/zha/core/const.py
|
||||
homeassistant/components/zha/core/device.py
|
||||
homeassistant/components/zha/core/listeners.py
|
||||
homeassistant/components/zha/core/gateway.py
|
||||
homeassistant/components/*/zha.py
|
||||
|
||||
homeassistant/components/zigbee.py
|
||||
homeassistant/components/*/zigbee.py
|
||||
|
||||
homeassistant/components/zoneminder/*
|
||||
homeassistant/components/*/zoneminder.py
|
||||
|
||||
homeassistant/components/tuya.py
|
||||
homeassistant/components/*/tuya.py
|
||||
@@ -443,6 +477,7 @@ omit =
|
||||
homeassistant/components/*/spider.py
|
||||
|
||||
homeassistant/components/air_quality/opensensemap.py
|
||||
homeassistant/components/air_quality/nilu.py
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/canary.py
|
||||
homeassistant/components/alarm_control_panel/concord232.py
|
||||
@@ -519,7 +554,6 @@ omit =
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/google_maps.py
|
||||
homeassistant/components/device_tracker/googlehome.py
|
||||
homeassistant/components/device_tracker/gpslogger.py
|
||||
homeassistant/components/device_tracker/hitron_coda.py
|
||||
homeassistant/components/device_tracker/huawei_router.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
@@ -536,6 +570,7 @@ omit =
|
||||
homeassistant/components/device_tracker/sky_hub.py
|
||||
homeassistant/components/device_tracker/snmp.py
|
||||
homeassistant/components/device_tracker/swisscom.py
|
||||
homeassistant/components/device_tracker/synology_srm.py
|
||||
homeassistant/components/device_tracker/tado.py
|
||||
homeassistant/components/device_tracker/thomson.py
|
||||
homeassistant/components/device_tracker/tile.py
|
||||
@@ -558,6 +593,7 @@ omit =
|
||||
homeassistant/components/image_processing/dlib_face_identify.py
|
||||
homeassistant/components/image_processing/seven_segments.py
|
||||
homeassistant/components/image_processing/tensorflow.py
|
||||
homeassistant/components/image_processing/qrcode.py
|
||||
homeassistant/components/keyboard_remote.py
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/light/avion.py
|
||||
@@ -565,6 +601,7 @@ omit =
|
||||
homeassistant/components/light/blinkt.py
|
||||
homeassistant/components/light/decora_wifi.py
|
||||
homeassistant/components/light/decora.py
|
||||
homeassistant/components/light/everlights.py
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/futurenow.py
|
||||
homeassistant/components/light/greenwave.py
|
||||
@@ -637,7 +674,6 @@ omit =
|
||||
homeassistant/components/media_player/pioneer.py
|
||||
homeassistant/components/media_player/pjlink.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/roku.py
|
||||
homeassistant/components/media_player/russound_rio.py
|
||||
homeassistant/components/media_player/russound_rnet.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
@@ -704,7 +740,6 @@ omit =
|
||||
homeassistant/components/sensor/aftership.py
|
||||
homeassistant/components/sensor/airvisual.py
|
||||
homeassistant/components/sensor/alpha_vantage.py
|
||||
homeassistant/components/sensor/ambient_station.py
|
||||
homeassistant/components/sensor/arest.py
|
||||
homeassistant/components/sensor/arwn.py
|
||||
homeassistant/components/sensor/bbox.py
|
||||
@@ -729,7 +764,6 @@ omit =
|
||||
homeassistant/components/sensor/dht.py
|
||||
homeassistant/components/sensor/discogs.py
|
||||
homeassistant/components/sensor/dnsip.py
|
||||
homeassistant/components/sensor/dovado.py
|
||||
homeassistant/components/sensor/domain_expiry.py
|
||||
homeassistant/components/sensor/dte_energy_bridge.py
|
||||
homeassistant/components/sensor/dublin_bus_transport.py
|
||||
@@ -766,6 +800,7 @@ omit =
|
||||
homeassistant/components/sensor/hp_ilo.py
|
||||
homeassistant/components/sensor/htu21d.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/iliad_italy.py
|
||||
homeassistant/components/sensor/imap_email_content.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/influxdb.py
|
||||
@@ -820,7 +855,9 @@ omit =
|
||||
homeassistant/components/sensor/qnap.py
|
||||
homeassistant/components/sensor/radarr.py
|
||||
homeassistant/components/sensor/rainbird.py
|
||||
homeassistant/components/sensor/recollect_waste.py
|
||||
homeassistant/components/sensor/ripple.py
|
||||
homeassistant/components/sensor/rova.py
|
||||
homeassistant/components/sensor/rtorrent.py
|
||||
homeassistant/components/sensor/ruter.py
|
||||
homeassistant/components/sensor/scrape.py
|
||||
@@ -859,7 +896,6 @@ omit =
|
||||
homeassistant/components/sensor/time_date.py
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/trafikverket_weatherstation.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/travisci.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
@@ -904,7 +940,6 @@ omit =
|
||||
homeassistant/components/switch/switchmate.py
|
||||
homeassistant/components/switch/telnet.py
|
||||
homeassistant/components/switch/tplink.py
|
||||
homeassistant/components/switch/transmission.py
|
||||
homeassistant/components/switch/vesync.py
|
||||
homeassistant/components/telegram_bot/*
|
||||
homeassistant/components/thingspeak.py
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -78,6 +78,7 @@ venv
|
||||
.venv
|
||||
Pipfile*
|
||||
share/*
|
||||
Scripts/
|
||||
|
||||
# vimmy stuff
|
||||
*.swp
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[settings]
|
||||
multi_line_output=4
|
||||
10
CODEOWNERS
10
CODEOWNERS
@@ -95,6 +95,7 @@ homeassistant/components/notify/syslog.py @fabaff
|
||||
homeassistant/components/notify/xmpp.py @fabaff
|
||||
homeassistant/components/notify/yessssms.py @flowolf
|
||||
homeassistant/components/plant.py @ChristianKuehnel
|
||||
homeassistant/components/remote/harmony.py @ehendrix23
|
||||
homeassistant/components/scene/lifx_cloud.py @amelchio
|
||||
homeassistant/components/sensor/airvisual.py @bachya
|
||||
homeassistant/components/sensor/alpha_vantage.py @fabaff
|
||||
@@ -152,6 +153,7 @@ homeassistant/components/weather/openweathermap.py @fabaff
|
||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||
|
||||
# A
|
||||
homeassistant/components/ambient_station/* @bachya
|
||||
homeassistant/components/arduino.py @fabaff
|
||||
homeassistant/components/*/arduino.py @fabaff
|
||||
homeassistant/components/*/arest.py @fabaff
|
||||
@@ -185,7 +187,6 @@ homeassistant/components/edp_redy.py @abmantis
|
||||
homeassistant/components/eight_sleep.py @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
homeassistant/components/esphome/*.py @OttoWinter
|
||||
homeassistant/components/*/esphome.py @OttoWinter
|
||||
|
||||
# H
|
||||
homeassistant/components/hive.py @Rendili @KJonline
|
||||
@@ -219,7 +220,6 @@ homeassistant/components/*/ness_alarm.py @nickw444
|
||||
|
||||
# O
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/*/openuv.py @bachya
|
||||
|
||||
# P
|
||||
homeassistant/components/point/* @fredrike
|
||||
@@ -231,13 +231,12 @@ homeassistant/components/*/qwikswitch.py @kellerza
|
||||
|
||||
# R
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/*/rainmachine.py @bachya
|
||||
homeassistant/components/*/random.py @fabaff
|
||||
homeassistant/components/*/rfxtrx.py @danielhiversen
|
||||
|
||||
# S
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/*/simplisafe.py @bachya
|
||||
homeassistant/components/smartthings/* @andrewsayre
|
||||
|
||||
# T
|
||||
homeassistant/components/tahoma.py @philklei
|
||||
@@ -272,8 +271,7 @@ homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
||||
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
||||
|
||||
# Z
|
||||
homeassistant/components/zoneminder/ @rohankapoorcom
|
||||
homeassistant/components/*/zoneminder.py @rohankapoorcom
|
||||
homeassistant/components/zoneminder/* @rohankapoorcom
|
||||
|
||||
# Other code
|
||||
homeassistant/scripts/check_config.py @kellerza
|
||||
|
||||
@@ -16,7 +16,6 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
|
||||
VOLUME /config
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy build scripts
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""Home Assistant auth provider."""
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
import logging
|
||||
|
||||
from typing import Any, Dict, List, Optional, Set, cast # noqa: F401
|
||||
|
||||
import bcrypt
|
||||
import voluptuous as vol
|
||||
@@ -51,6 +53,18 @@ class Data:
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
self._data = None # type: Optional[Dict[str, Any]]
|
||||
# Legacy mode will allow usernames to start/end with whitespace
|
||||
# and will compare usernames case-insensitive.
|
||||
# Remove in 2020 or when we launch 1.0.
|
||||
self.is_legacy = False
|
||||
|
||||
@callback
|
||||
def normalize_username(self, username: str) -> str:
|
||||
"""Normalize a username based on the mode."""
|
||||
if self.is_legacy:
|
||||
return username
|
||||
|
||||
return username.strip().casefold()
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
@@ -61,6 +75,37 @@ class Data:
|
||||
'users': []
|
||||
}
|
||||
|
||||
seen = set() # type: Set[str]
|
||||
|
||||
for user in data['users']:
|
||||
username = user['username']
|
||||
|
||||
# check if we have duplicates
|
||||
folded = username.casefold()
|
||||
|
||||
if folded in seen:
|
||||
self.is_legacy = True
|
||||
|
||||
logging.getLogger(__name__).warning(
|
||||
"Home Assistant auth provider is running in legacy mode "
|
||||
"because we detected usernames that are case-insensitive"
|
||||
"equivalent. Please change the username: '%s'.", username)
|
||||
|
||||
break
|
||||
|
||||
seen.add(folded)
|
||||
|
||||
# check if we have unstripped usernames
|
||||
if username != username.strip():
|
||||
self.is_legacy = True
|
||||
|
||||
logging.getLogger(__name__).warning(
|
||||
"Home Assistant auth provider is running in legacy mode "
|
||||
"because we detected usernames that start or end in a "
|
||||
"space. Please change the username: '%s'.", username)
|
||||
|
||||
break
|
||||
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
@@ -73,12 +118,13 @@ class Data:
|
||||
|
||||
Raises InvalidAuth if auth invalid.
|
||||
"""
|
||||
username = self.normalize_username(username)
|
||||
dummy = b'$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO'
|
||||
found = None
|
||||
|
||||
# Compare all users to avoid timing attacks.
|
||||
for user in self.users:
|
||||
if username == user['username']:
|
||||
if self.normalize_username(user['username']) == username:
|
||||
found = user
|
||||
|
||||
if found is None:
|
||||
@@ -105,7 +151,10 @@ class Data:
|
||||
|
||||
def add_auth(self, username: str, password: str) -> None:
|
||||
"""Add a new authenticated user/pass."""
|
||||
if any(user['username'] == username for user in self.users):
|
||||
username = self.normalize_username(username)
|
||||
|
||||
if any(self.normalize_username(user['username']) == username
|
||||
for user in self.users):
|
||||
raise InvalidUser
|
||||
|
||||
self.users.append({
|
||||
@@ -116,9 +165,11 @@ class Data:
|
||||
@callback
|
||||
def async_remove_auth(self, username: str) -> None:
|
||||
"""Remove authentication."""
|
||||
username = self.normalize_username(username)
|
||||
|
||||
index = None
|
||||
for i, user in enumerate(self.users):
|
||||
if user['username'] == username:
|
||||
if self.normalize_username(user['username']) == username:
|
||||
index = i
|
||||
break
|
||||
|
||||
@@ -132,8 +183,10 @@ class Data:
|
||||
|
||||
Raises InvalidUser if user cannot be found.
|
||||
"""
|
||||
username = self.normalize_username(username)
|
||||
|
||||
for user in self.users:
|
||||
if user['username'] == username:
|
||||
if self.normalize_username(user['username']) == username:
|
||||
user['password'] = self.hash_password(
|
||||
new_password, True).decode()
|
||||
break
|
||||
@@ -178,10 +231,15 @@ class HassAuthProvider(AuthProvider):
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Dict[str, str]) -> Credentials:
|
||||
"""Get credentials based on the flow result."""
|
||||
username = flow_result['username']
|
||||
if self.data is None:
|
||||
await self.async_initialize()
|
||||
assert self.data is not None
|
||||
|
||||
norm_username = self.data.normalize_username
|
||||
username = norm_username(flow_result['username'])
|
||||
|
||||
for credential in await self.async_credentials():
|
||||
if credential.data['username'] == username:
|
||||
if norm_username(credential.data['username']) == username:
|
||||
return credential
|
||||
|
||||
# Create new credentials.
|
||||
|
||||
@@ -18,6 +18,7 @@ from homeassistant.util.logging import AsyncHandler
|
||||
from homeassistant.util.package import async_get_user_site, is_virtual_env
|
||||
from homeassistant.util.yaml import clear_secret_cache
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -153,6 +154,34 @@ async def async_from_config_dict(config: Dict[str, Any],
|
||||
stop = time()
|
||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
|
||||
|
||||
# TEMP: warn users for invalid slugs
|
||||
# Remove after 0.94 or 1.0
|
||||
if cv.INVALID_SLUGS_FOUND or cv.INVALID_ENTITY_IDS_FOUND:
|
||||
msg = []
|
||||
|
||||
if cv.INVALID_ENTITY_IDS_FOUND:
|
||||
msg.append(
|
||||
"Your configuration contains invalid entity ID references. "
|
||||
"Please find and update the following. "
|
||||
"This will become a breaking change."
|
||||
)
|
||||
msg.append('\n'.join('- {} -> {}'.format(*item)
|
||||
for item
|
||||
in cv.INVALID_ENTITY_IDS_FOUND.items()))
|
||||
|
||||
if cv.INVALID_SLUGS_FOUND:
|
||||
msg.append(
|
||||
"Your configuration contains invalid slugs. "
|
||||
"Please find and update the following. "
|
||||
"This will become a breaking change."
|
||||
)
|
||||
msg.append('\n'.join('- {} -> {}'.format(*item)
|
||||
for item in cv.INVALID_SLUGS_FOUND.items()))
|
||||
|
||||
hass.components.persistent_notification.async_create(
|
||||
'\n\n'.join(msg), "Config Warning", "config_warning"
|
||||
)
|
||||
|
||||
return hass
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['abodepy==0.14.0']
|
||||
REQUIREMENTS = ['abodepy==0.15.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -46,8 +46,8 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
|
||||
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
|
||||
vol.Required(CONF_ADS_TYPE):
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]),
|
||||
vol.Required(CONF_ADS_VALUE): cv.match_all,
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE, ADSTYPE_BOOL]),
|
||||
vol.Required(CONF_ADS_VALUE): vol.Coerce(int),
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_AQI = 'air_quality_index'
|
||||
ATTR_ATTRIBUTION = 'attribution'
|
||||
ATTR_C02 = 'carbon_dioxide'
|
||||
ATTR_CO2 = 'carbon_dioxide'
|
||||
ATTR_CO = 'carbon_monoxide'
|
||||
ATTR_N2O = 'nitrogen_oxide'
|
||||
ATTR_NO = 'nitrogen_monoxide'
|
||||
@@ -35,7 +35,7 @@ SCAN_INTERVAL = timedelta(seconds=30)
|
||||
PROP_TO_ATTR = {
|
||||
'air_quality_index': ATTR_AQI,
|
||||
'attribution': ATTR_ATTRIBUTION,
|
||||
'carbon_dioxide': ATTR_C02,
|
||||
'carbon_dioxide': ATTR_CO2,
|
||||
'carbon_monoxide': ATTR_CO,
|
||||
'nitrogen_oxide': ATTR_N2O,
|
||||
'nitrogen_monoxide': ATTR_NO,
|
||||
|
||||
252
homeassistant/components/air_quality/nilu.py
Normal file
252
homeassistant/components/air_quality/nilu.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
Sensor for checking the air quality around Norway.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/air_quality.nilu/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.air_quality import (
|
||||
PLATFORM_SCHEMA, AirQualityEntity)
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_SHOW_ON_MAP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['niluclient==0.1.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_AREA = 'area'
|
||||
ATTR_POLLUTION_INDEX = 'nilu_pollution_index'
|
||||
ATTRIBUTION = "Data provided by luftkvalitet.info and nilu.no"
|
||||
|
||||
CONF_AREA = 'area'
|
||||
CONF_STATION = 'stations'
|
||||
|
||||
DEFAULT_NAME = 'NILU'
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
CONF_ALLOWED_AREAS = [
|
||||
'Bergen',
|
||||
'Birkenes',
|
||||
'Bodø',
|
||||
'Brumunddal',
|
||||
'Bærum',
|
||||
'Drammen',
|
||||
'Elverum',
|
||||
'Fredrikstad',
|
||||
'Gjøvik',
|
||||
'Grenland',
|
||||
'Halden',
|
||||
'Hamar',
|
||||
'Harstad',
|
||||
'Hurdal',
|
||||
'Karasjok',
|
||||
'Kristiansand',
|
||||
'Kårvatn',
|
||||
'Lillehammer',
|
||||
'Lillesand',
|
||||
'Lillestrøm',
|
||||
'Lørenskog',
|
||||
'Mo i Rana',
|
||||
'Moss',
|
||||
'Narvik',
|
||||
'Oslo',
|
||||
'Prestebakke',
|
||||
'Sandve',
|
||||
'Sarpsborg',
|
||||
'Stavanger',
|
||||
'Sør-Varanger',
|
||||
'Tromsø',
|
||||
'Trondheim',
|
||||
'Tustervatn',
|
||||
'Zeppelinfjellet',
|
||||
'Ålesund',
|
||||
]
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Inclusive(CONF_LATITUDE, 'coordinates',
|
||||
'Latitude and longitude must exist together'): cv.latitude,
|
||||
vol.Inclusive(CONF_LONGITUDE, 'coordinates',
|
||||
'Latitude and longitude must exist together'): cv.longitude,
|
||||
vol.Exclusive(CONF_AREA, 'station_collection',
|
||||
'Can only configure one specific station or '
|
||||
'stations in a specific area pr sensor. '
|
||||
'Please only configure station or area.'
|
||||
): vol.All(cv.string, vol.In(CONF_ALLOWED_AREAS)),
|
||||
vol.Exclusive(CONF_STATION, 'station_collection',
|
||||
'Can only configure one specific station or '
|
||||
'stations in a specific area pr sensor. '
|
||||
'Please only configure station or area.'
|
||||
): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the NILU air quality sensor."""
|
||||
import niluclient as nilu
|
||||
name = config.get(CONF_NAME)
|
||||
area = config.get(CONF_AREA)
|
||||
stations = config.get(CONF_STATION)
|
||||
show_on_map = config.get(CONF_SHOW_ON_MAP)
|
||||
|
||||
sensors = []
|
||||
|
||||
if area:
|
||||
stations = nilu.lookup_stations_in_area(area)
|
||||
elif not area and not stations:
|
||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
location_client = nilu.create_location_client(latitude, longitude)
|
||||
stations = location_client.station_names
|
||||
|
||||
for station in stations:
|
||||
client = NiluData(nilu.create_station_client(station))
|
||||
client.update()
|
||||
if client.data.sensors:
|
||||
sensors.append(NiluSensor(client, name, show_on_map))
|
||||
else:
|
||||
_LOGGER.warning("%s didn't give any sensors results", station)
|
||||
|
||||
add_entities(sensors, True)
|
||||
|
||||
|
||||
class NiluData:
|
||||
"""Class for handling the data retrieval."""
|
||||
|
||||
def __init__(self, api):
|
||||
"""Initialize the data object."""
|
||||
self.api = api
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
"""Get data cached in client."""
|
||||
return self.api.data
|
||||
|
||||
@Throttle(SCAN_INTERVAL)
|
||||
def update(self):
|
||||
"""Get the latest data from nilu API."""
|
||||
self.api.update()
|
||||
|
||||
|
||||
class NiluSensor(AirQualityEntity):
|
||||
"""Single nilu station air sensor."""
|
||||
|
||||
def __init__(self, api_data: NiluData, name: str, show_on_map: bool):
|
||||
"""Initialize the sensor."""
|
||||
self._api = api_data
|
||||
self._name = "{} {}".format(name, api_data.data.name)
|
||||
self._max_aqi = None
|
||||
self._attrs = {}
|
||||
|
||||
if show_on_map:
|
||||
self._attrs[CONF_LATITUDE] = api_data.data.latitude
|
||||
self._attrs[CONF_LONGITUDE] = api_data.data.longitude
|
||||
|
||||
@property
|
||||
def attribution(self) -> str:
|
||||
"""Return the attribution."""
|
||||
return ATTRIBUTION
|
||||
|
||||
@property
|
||||
def device_state_attributes(self) -> dict:
|
||||
"""Return other details about the sensor state."""
|
||||
return self._attrs
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def air_quality_index(self) -> str:
|
||||
"""Return the Air Quality Index (AQI)."""
|
||||
return self._max_aqi
|
||||
|
||||
@property
|
||||
def carbon_monoxide(self) -> str:
|
||||
"""Return the CO (carbon monoxide) level."""
|
||||
from niluclient import CO
|
||||
return self.get_component_state(CO)
|
||||
|
||||
@property
|
||||
def carbon_dioxide(self) -> str:
|
||||
"""Return the CO2 (carbon dioxide) level."""
|
||||
from niluclient import CO2
|
||||
return self.get_component_state(CO2)
|
||||
|
||||
@property
|
||||
def nitrogen_oxide(self) -> str:
|
||||
"""Return the N2O (nitrogen oxide) level."""
|
||||
from niluclient import NOX
|
||||
return self.get_component_state(NOX)
|
||||
|
||||
@property
|
||||
def nitrogen_monoxide(self) -> str:
|
||||
"""Return the NO (nitrogen monoxide) level."""
|
||||
from niluclient import NO
|
||||
return self.get_component_state(NO)
|
||||
|
||||
@property
|
||||
def nitrogen_dioxide(self) -> str:
|
||||
"""Return the NO2 (nitrogen dioxide) level."""
|
||||
from niluclient import NO2
|
||||
return self.get_component_state(NO2)
|
||||
|
||||
@property
|
||||
def ozone(self) -> str:
|
||||
"""Return the O3 (ozone) level."""
|
||||
from niluclient import OZONE
|
||||
return self.get_component_state(OZONE)
|
||||
|
||||
@property
|
||||
def particulate_matter_2_5(self) -> str:
|
||||
"""Return the particulate matter 2.5 level."""
|
||||
from niluclient import PM25
|
||||
return self.get_component_state(PM25)
|
||||
|
||||
@property
|
||||
def particulate_matter_10(self) -> str:
|
||||
"""Return the particulate matter 10 level."""
|
||||
from niluclient import PM10
|
||||
return self.get_component_state(PM10)
|
||||
|
||||
@property
|
||||
def particulate_matter_0_1(self) -> str:
|
||||
"""Return the particulate matter 0.1 level."""
|
||||
from niluclient import PM1
|
||||
return self.get_component_state(PM1)
|
||||
|
||||
@property
|
||||
def sulphur_dioxide(self) -> str:
|
||||
"""Return the SO2 (sulphur dioxide) level."""
|
||||
from niluclient import SO2
|
||||
return self.get_component_state(SO2)
|
||||
|
||||
def get_component_state(self, component_name: str) -> str:
|
||||
"""Return formatted value of specified component."""
|
||||
if component_name in self._api.data.sensors:
|
||||
sensor = self._api.data.sensors[component_name]
|
||||
return sensor.value
|
||||
return None
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update the sensor."""
|
||||
import niluclient as nilu
|
||||
self._api.update()
|
||||
|
||||
sensors = self._api.data.sensors.values()
|
||||
if sensors:
|
||||
max_index = max([s.pollution_index for s in sensors])
|
||||
self._max_aqi = max_index
|
||||
self._attrs[ATTR_POLLUTION_INDEX] = \
|
||||
nilu.POLLUTION_INDEX[self._max_aqi]
|
||||
|
||||
self._attrs[ATTR_AREA] = self._api.data.area
|
||||
@@ -13,7 +13,8 @@ from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
from homeassistant.helpers.config_validation import ( # noqa
|
||||
PLATFORM_SCHEMA_BASE, PLATFORM_SCHEMA_2 as PLATFORM_SCHEMA)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
@@ -21,6 +22,8 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
DOMAIN = 'alarm_control_panel'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
ATTR_CHANGED_BY = 'changed_by'
|
||||
FORMAT_TEXT = 'text'
|
||||
FORMAT_NUMBER = 'number'
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ https://home-assistant.io/components/alarm_control_panel.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
||||
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
@@ -31,7 +31,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
add_entities(alarm_devices)
|
||||
|
||||
|
||||
class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
def __init__(self, data, device, name):
|
||||
@@ -57,6 +57,11 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
state = None
|
||||
return state
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._device.set_standby()
|
||||
|
||||
@@ -99,7 +99,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -13,7 +13,7 @@ import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
@@ -57,7 +57,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._websession = async_get_clientsession(self._hass)
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
self._alarm = Alarmdotcom(
|
||||
username, password, self._websession, hass.loop)
|
||||
|
||||
@@ -81,8 +81,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@@ -93,7 +93,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
if self._alarm.state.lower() == 'armed away':
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
return STATE_UNKNOWN
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.components.arlo import (
|
||||
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,6 +25,7 @@ ARMED = 'armed'
|
||||
|
||||
CONF_HOME_MODE_NAME = 'home_mode_name'
|
||||
CONF_AWAY_MODE_NAME = 'away_mode_name'
|
||||
CONF_NIGHT_MODE_NAME = 'night_mode_name'
|
||||
|
||||
DEPENDENCIES = ['arlo']
|
||||
|
||||
@@ -35,6 +36,7 @@ ICON = 'mdi:security'
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string,
|
||||
vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string,
|
||||
vol.Optional(CONF_NIGHT_MODE_NAME, default=ARMED): cv.string,
|
||||
})
|
||||
|
||||
|
||||
@@ -47,21 +49,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
home_mode_name = config.get(CONF_HOME_MODE_NAME)
|
||||
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
|
||||
night_mode_name = config.get(CONF_NIGHT_MODE_NAME)
|
||||
base_stations = []
|
||||
for base_station in arlo.base_stations:
|
||||
base_stations.append(ArloBaseStation(base_station, home_mode_name,
|
||||
away_mode_name))
|
||||
away_mode_name, night_mode_name))
|
||||
add_entities(base_stations, True)
|
||||
|
||||
|
||||
class ArloBaseStation(AlarmControlPanel):
|
||||
"""Representation of an Arlo Alarm Control Panel."""
|
||||
|
||||
def __init__(self, data, home_mode_name, away_mode_name):
|
||||
def __init__(self, data, home_mode_name, away_mode_name, night_mode_name):
|
||||
"""Initialize the alarm control panel."""
|
||||
self._base_station = data
|
||||
self._home_mode_name = home_mode_name
|
||||
self._away_mode_name = away_mode_name
|
||||
self._night_mode_name = night_mode_name
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
@@ -105,6 +109,10 @@ class ArloBaseStation(AlarmControlPanel):
|
||||
"""Send arm home command. Uses custom mode."""
|
||||
self._base_station.mode = self._home_mode_name
|
||||
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command. Uses custom mode."""
|
||||
self._base_station.mode = self._night_mode_name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the base station."""
|
||||
@@ -128,4 +136,6 @@ class ArloBaseStation(AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
if mode == self._away_mode_name:
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
if mode == self._night_mode_name:
|
||||
return STATE_ALARM_ARMED_NIGHT
|
||||
return mode
|
||||
|
||||
@@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.concord232/
|
||||
"""
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
|
||||
REQUIREMENTS = ['concord232==0.15']
|
||||
|
||||
@@ -26,7 +25,7 @@ DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_NAME = 'CONCORD232'
|
||||
DEFAULT_PORT = 5007
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
SCAN_INTERVAL = datetime.timedelta(seconds=10)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||
@@ -44,33 +43,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
url = 'http://{}:{}'.format(host, port)
|
||||
|
||||
try:
|
||||
add_entities([Concord232Alarm(hass, url, name)])
|
||||
add_entities([Concord232Alarm(url, name)], True)
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
||||
return
|
||||
|
||||
|
||||
class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
"""Representation of the Concord232-based alarm panel."""
|
||||
|
||||
def __init__(self, hass, url, name):
|
||||
def __init__(self, url, name):
|
||||
"""Initialize the Concord232 alarm panel."""
|
||||
from concord232 import client as concord232_client
|
||||
|
||||
self._state = STATE_UNKNOWN
|
||||
self._hass = hass
|
||||
self._state = None
|
||||
self._name = name
|
||||
self._url = url
|
||||
|
||||
try:
|
||||
client = concord232_client.Client(self._url)
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
||||
|
||||
self._alarm = client
|
||||
self._alarm = concord232_client.Client(self._url)
|
||||
self._alarm.partitions = self._alarm.list_partitions()
|
||||
self._alarm.last_partition_update = datetime.datetime.now()
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -80,7 +70,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the characters if code is defined."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
@@ -94,22 +84,17 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
|
||||
dict(host=self._url, reason=ex))
|
||||
newstate = STATE_UNKNOWN
|
||||
return
|
||||
except IndexError:
|
||||
_LOGGER.error("Concord232 reports no partitions")
|
||||
newstate = STATE_UNKNOWN
|
||||
return
|
||||
|
||||
if part['arming_level'] == 'Off':
|
||||
newstate = STATE_ALARM_DISARMED
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif 'Home' in part['arming_level']:
|
||||
newstate = STATE_ALARM_ARMED_HOME
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
newstate = STATE_ALARM_ARMED_AWAY
|
||||
|
||||
if not newstate == self._state:
|
||||
_LOGGER.info("State change from %s to %s", self._state, newstate)
|
||||
self._state = newstate
|
||||
return self._state
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
||||
@@ -116,7 +116,7 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the alarm code format."""
|
||||
return '^[0-9]{4}([0-9]{2})?$'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -104,7 +104,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
"""Regex for code format or None if no code is required."""
|
||||
if self._code:
|
||||
return None
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -76,7 +76,7 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
await self._home.set_security_zones_activation(True, False)
|
||||
await self._home.set_security_zones_activation(False, True)
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
|
||||
@@ -82,8 +82,8 @@ class IAlarmPanel(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -129,8 +129,8 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
||||
@@ -207,8 +207,8 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
||||
@@ -241,8 +241,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
||||
@@ -59,7 +59,7 @@ class NessAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the regex for code format or None if no code is required."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -70,7 +70,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -64,7 +64,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the regex for code format or None if no code is required."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME,
|
||||
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, CONF_NAME,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
self._name = name
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
self._client = TotalConnectClient.TotalConnectClient(
|
||||
username, password)
|
||||
|
||||
@@ -85,7 +85,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
elif status == self._client.DISARMING:
|
||||
state = STATE_ALARM_DISARMING
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
state = None
|
||||
|
||||
self._state = state
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -44,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Verisure alarm panel."""
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
||||
self._changed_by = None
|
||||
|
||||
@@ -61,7 +60,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def changed_by(self):
|
||||
|
||||
@@ -9,8 +9,7 @@ import logging
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,7 +51,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
elif wink_state == "night":
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
state = None
|
||||
return state
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
|
||||
@@ -5,19 +5,19 @@ For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alert/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
|
||||
ATTR_MESSAGE, ATTR_TITLE, ATTR_DATA, DOMAIN as DOMAIN_NOTIFY)
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.helpers import service, event
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,6 +30,8 @@ CONF_REPEAT = 'repeat'
|
||||
CONF_SKIP_FIRST = 'skip_first'
|
||||
CONF_ALERT_MESSAGE = 'message'
|
||||
CONF_DONE_MESSAGE = 'done_message'
|
||||
CONF_TITLE = 'title'
|
||||
CONF_DATA = 'data'
|
||||
|
||||
DEFAULT_CAN_ACK = True
|
||||
DEFAULT_SKIP_FIRST = False
|
||||
@@ -43,15 +45,14 @@ ALERT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_SKIP_FIRST, default=DEFAULT_SKIP_FIRST): cv.boolean,
|
||||
vol.Optional(CONF_ALERT_MESSAGE): cv.template,
|
||||
vol.Optional(CONF_DONE_MESSAGE): cv.template,
|
||||
vol.Optional(CONF_TITLE): cv.template,
|
||||
vol.Optional(CONF_DATA): dict,
|
||||
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: ALERT_SCHEMA,
|
||||
}),
|
||||
DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
ALERT_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
})
|
||||
@@ -79,12 +80,14 @@ async def async_setup(hass, config):
|
||||
done_message_template = cfg.get(CONF_DONE_MESSAGE)
|
||||
notifiers = cfg.get(CONF_NOTIFIERS)
|
||||
can_ack = cfg.get(CONF_CAN_ACK)
|
||||
title_template = cfg.get(CONF_TITLE)
|
||||
data = cfg.get(CONF_DATA)
|
||||
|
||||
entities.append(Alert(hass, object_id, name,
|
||||
watched_entity_id, alert_state, repeat,
|
||||
skip_first, message_template,
|
||||
done_message_template, notifiers,
|
||||
can_ack))
|
||||
can_ack, title_template, data))
|
||||
|
||||
if not entities:
|
||||
return False
|
||||
@@ -129,12 +132,14 @@ class Alert(ToggleEntity):
|
||||
|
||||
def __init__(self, hass, entity_id, name, watched_entity_id,
|
||||
state, repeat, skip_first, message_template,
|
||||
done_message_template, notifiers, can_ack):
|
||||
done_message_template, notifiers, can_ack, title_template,
|
||||
data):
|
||||
"""Initialize the alert."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self._alert_state = state
|
||||
self._skip_first = skip_first
|
||||
self._data = data
|
||||
|
||||
self._message_template = message_template
|
||||
if self._message_template is not None:
|
||||
@@ -144,6 +149,10 @@ class Alert(ToggleEntity):
|
||||
if self._done_message_template is not None:
|
||||
self._done_message_template.hass = hass
|
||||
|
||||
self._title_template = title_template
|
||||
if self._title_template is not None:
|
||||
self._title_template.hass = hass
|
||||
|
||||
self._notifiers = notifiers
|
||||
self._can_ack = can_ack
|
||||
|
||||
@@ -253,9 +262,20 @@ class Alert(ToggleEntity):
|
||||
await self._send_notification_message(message)
|
||||
|
||||
async def _send_notification_message(self, message):
|
||||
|
||||
msg_payload = {ATTR_MESSAGE: message}
|
||||
|
||||
if self._title_template is not None:
|
||||
title = self._title_template.async_render()
|
||||
msg_payload.update({ATTR_TITLE: title})
|
||||
if self._data:
|
||||
msg_payload.update({ATTR_DATA: self._data})
|
||||
|
||||
_LOGGER.debug(msg_payload)
|
||||
|
||||
for target in self._notifiers:
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: message})
|
||||
DOMAIN_NOTIFY, target, msg_payload)
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Async Unacknowledge alert."""
|
||||
|
||||
@@ -72,6 +72,6 @@ async def async_setup(hass, config):
|
||||
pass
|
||||
else:
|
||||
smart_home_config = smart_home_config or SMART_HOME_SCHEMA({})
|
||||
smart_home.async_setup(hass, smart_home_config)
|
||||
await smart_home.async_setup(hass, smart_home_config)
|
||||
|
||||
return True
|
||||
|
||||
@@ -27,8 +27,9 @@ from homeassistant.const import (
|
||||
CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, STATE_UNLOCKED,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL)
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON,
|
||||
STATE_UNAVAILABLE, STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
MATCH_ALL)
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
@@ -393,6 +394,37 @@ class _AlexaInterface:
|
||||
}
|
||||
|
||||
|
||||
class _AlexaEndpointHealth(_AlexaInterface):
|
||||
"""Implements Alexa.EndpointHealth.
|
||||
|
||||
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-when-alexa-requests-it
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity):
|
||||
super().__init__(entity)
|
||||
self.hass = hass
|
||||
|
||||
def name(self):
|
||||
return 'Alexa.EndpointHealth'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'connectivity'}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
return False
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'connectivity':
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
if self.entity.state == STATE_UNAVAILABLE:
|
||||
return {'value': 'UNREACHABLE'}
|
||||
return {'value': 'OK'}
|
||||
|
||||
|
||||
class _AlexaPowerController(_AlexaInterface):
|
||||
"""Implements Alexa.PowerController.
|
||||
|
||||
@@ -769,7 +801,8 @@ class _GenericCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.OTHER]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
return [_AlexaPowerController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(switch.DOMAIN)
|
||||
@@ -778,7 +811,8 @@ class _SwitchCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.SWITCH]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
return [_AlexaPowerController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(climate.DOMAIN)
|
||||
@@ -792,6 +826,7 @@ class _ClimateCapabilities(_AlexaEntity):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
yield _AlexaThermostatController(self.hass, self.entity)
|
||||
yield _AlexaTemperatureSensor(self.hass, self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(cover.DOMAIN)
|
||||
@@ -804,6 +839,7 @@ class _CoverCapabilities(_AlexaEntity):
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & cover.SUPPORT_SET_POSITION:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(light.DOMAIN)
|
||||
@@ -821,6 +857,7 @@ class _LightCapabilities(_AlexaEntity):
|
||||
yield _AlexaColorController(self.entity)
|
||||
if supported & light.SUPPORT_COLOR_TEMP:
|
||||
yield _AlexaColorTemperatureController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(fan.DOMAIN)
|
||||
@@ -833,6 +870,7 @@ class _FanCapabilities(_AlexaEntity):
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(lock.DOMAIN)
|
||||
@@ -841,7 +879,8 @@ class _LockCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.SMARTLOCK]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaLockController(self.entity)]
|
||||
return [_AlexaLockController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
|
||||
@@ -851,6 +890,7 @@ class _MediaPlayerCapabilities(_AlexaEntity):
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & media_player.SUPPORT_VOLUME_SET:
|
||||
@@ -913,6 +953,7 @@ class _SensorCapabilities(_AlexaEntity):
|
||||
TEMP_CELSIUS,
|
||||
):
|
||||
yield _AlexaTemperatureSensor(self.hass, self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(binary_sensor.DOMAIN)
|
||||
@@ -934,6 +975,8 @@ class _BinarySensorCapabilities(_AlexaEntity):
|
||||
elif sensor_type is self.TYPE_MOTION:
|
||||
yield _AlexaMotionSensor(self.hass, self.entity)
|
||||
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
def get_type(self):
|
||||
"""Return the type of binary sensor."""
|
||||
attrs = self.entity.attributes
|
||||
@@ -993,8 +1036,7 @@ class Config:
|
||||
self.entity_config = entity_config or {}
|
||||
|
||||
|
||||
@ha.callback
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Activate Smart Home functionality of Alexa component.
|
||||
|
||||
This is optional, triggered by having a `smart_home:` sub-section in the
|
||||
@@ -1020,8 +1062,7 @@ def async_setup(hass, config):
|
||||
hass.http.register_view(SmartHomeView(smart_home_config))
|
||||
|
||||
if AUTH_KEY in hass.data:
|
||||
hass.loop.create_task(
|
||||
async_enable_proactive_mode(hass, smart_home_config))
|
||||
await async_enable_proactive_mode(hass, smart_home_config)
|
||||
|
||||
|
||||
async def async_enable_proactive_mode(hass, smart_home_config):
|
||||
@@ -1337,8 +1378,7 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
return
|
||||
|
||||
headers = {
|
||||
"Authorization": "Bearer {}".format(token),
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
"Authorization": "Bearer {}".format(token)
|
||||
}
|
||||
|
||||
endpoint = alexa_entity.entity_id()
|
||||
@@ -1359,14 +1399,14 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
payload=payload)
|
||||
message.set_endpoint_full(token, endpoint)
|
||||
|
||||
message_str = json.dumps(message.serialize())
|
||||
message_serialized = message.serialize()
|
||||
|
||||
try:
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
|
||||
response = await session.post(config.endpoint,
|
||||
headers=headers,
|
||||
data=message_str,
|
||||
json=message_serialized,
|
||||
allow_redirects=True)
|
||||
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
@@ -1375,7 +1415,7 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
|
||||
response_text = await response.text()
|
||||
|
||||
_LOGGER.debug("Sent: %s", message_str)
|
||||
_LOGGER.debug("Sent: %s", json.dumps(message_serialized))
|
||||
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||
|
||||
if response.status != 202:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada",
|
||||
"invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es",
|
||||
"no_devices": "No s'ha trobat cap dispositiu al compte"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "Clau API",
|
||||
"app_key": "Clau d'aplicaci\u00f3"
|
||||
},
|
||||
"title": "Introdueix la teva informaci\u00f3"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Application Key and/or API Key already registered",
|
||||
"invalid_key": "Invalid API Key and/or Application Key",
|
||||
"no_devices": "No devices found in account"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"app_key": "Application Key"
|
||||
},
|
||||
"title": "Fill in your information"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||
"invalid_key": "Application \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||
"no_devices": "\uacc4\uc815\uc5d0 \uae30\uae30\uac00 \uc874\uc7ac\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API \ud0a4",
|
||||
"app_key": "Application \ud0a4"
|
||||
},
|
||||
"title": "\uc0ac\uc6a9\uc790 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert",
|
||||
"invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel",
|
||||
"no_devices": "Keng Apparater am Kont fonnt"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API Schl\u00ebssel",
|
||||
"app_key": "Applikatioun's Schl\u00ebssel"
|
||||
},
|
||||
"title": "F\u00ebllt \u00e4r Informatiounen aus"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d",
|
||||
"invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f",
|
||||
"no_devices": "\u0412 \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "\u041a\u043b\u044e\u0447 API",
|
||||
"app_key": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
},
|
||||
"title": "Ambient PWS"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a",
|
||||
"invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548",
|
||||
"no_devices": "\u5e33\u865f\u4e2d\u627e\u4e0d\u5230\u4efb\u4f55\u88dd\u7f6e"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "API \u5bc6\u9470",
|
||||
"app_key": "\u61c9\u7528\u5bc6\u9470"
|
||||
},
|
||||
"title": "\u586b\u5beb\u8cc7\u8a0a"
|
||||
}
|
||||
},
|
||||
"title": "\u74b0\u5883 PWS"
|
||||
}
|
||||
}
|
||||
212
homeassistant/components/ambient_station/__init__.py
Normal file
212
homeassistant/components/ambient_station/__init__.py
Normal file
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Support for Ambient Weather Station Service.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/ambient_station/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME, ATTR_LOCATION, CONF_API_KEY, CONF_MONITORED_CONDITIONS,
|
||||
CONF_UNIT_SYSTEM, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from .config_flow import configured_instances
|
||||
from .const import (
|
||||
ATTR_LAST_DATA, CONF_APP_KEY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI,
|
||||
UNITS_US)
|
||||
|
||||
REQUIREMENTS = ['aioambient==0.1.0']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_SOCKET_MIN_RETRY = 15
|
||||
|
||||
SENSOR_TYPES = {
|
||||
'24hourrainin': ['24 Hr Rain', 'in'],
|
||||
'baromabsin': ['Abs Pressure', 'inHg'],
|
||||
'baromrelin': ['Rel Pressure', 'inHg'],
|
||||
'battout': ['Battery', ''],
|
||||
'co2': ['co2', 'ppm'],
|
||||
'dailyrainin': ['Daily Rain', 'in'],
|
||||
'dewPoint': ['Dew Point', ['°F', '°C']],
|
||||
'eventrainin': ['Event Rain', 'in'],
|
||||
'feelsLike': ['Feels Like', ['°F', '°C']],
|
||||
'hourlyrainin': ['Hourly Rain Rate', 'in/hr'],
|
||||
'humidity': ['Humidity', '%'],
|
||||
'humidityin': ['Humidity In', '%'],
|
||||
'lastRain': ['Last Rain', ''],
|
||||
'maxdailygust': ['Max Gust', 'mph'],
|
||||
'monthlyrainin': ['Monthly Rain', 'in'],
|
||||
'solarradiation': ['Solar Rad', 'W/m^2'],
|
||||
'tempf': ['Temp', ['°F', '°C']],
|
||||
'tempinf': ['Inside Temp', ['°F', '°C']],
|
||||
'totalrainin': ['Lifetime Rain', 'in'],
|
||||
'uv': ['uv', 'Index'],
|
||||
'weeklyrainin': ['Weekly Rain', 'in'],
|
||||
'winddir': ['Wind Dir', '°'],
|
||||
'winddir_avg10m': ['Wind Dir Avg 10m', '°'],
|
||||
'winddir_avg2m': ['Wind Dir Avg 2m', 'mph'],
|
||||
'windgustdir': ['Gust Dir', '°'],
|
||||
'windgustmph': ['Wind Gust', 'mph'],
|
||||
'windspdmph_avg10m': ['Wind Avg 10m', 'mph'],
|
||||
'windspdmph_avg2m': ['Wind Avg 2m', 'mph'],
|
||||
'windspeedmph': ['Wind Speed', 'mph'],
|
||||
'yearlyrainin': ['Yearly Rain', 'in'],
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN:
|
||||
vol.Schema({
|
||||
vol.Required(CONF_APP_KEY):
|
||||
cv.string,
|
||||
vol.Required(CONF_API_KEY):
|
||||
cv.string,
|
||||
vol.Optional(
|
||||
CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_UNIT_SYSTEM):
|
||||
vol.In([UNITS_SI, UNITS_US]),
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Ambient PWS component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
if conf[CONF_APP_KEY] in configured_instances(hass):
|
||||
return True
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': SOURCE_IMPORT}, data=conf))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up the Ambient PWS as config entry."""
|
||||
from aioambient import Client
|
||||
from aioambient.errors import WebsocketConnectionError
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
try:
|
||||
ambient = AmbientStation(
|
||||
hass,
|
||||
config_entry,
|
||||
Client(
|
||||
config_entry.data[CONF_API_KEY],
|
||||
config_entry.data[CONF_APP_KEY], session),
|
||||
config_entry.data.get(
|
||||
CONF_MONITORED_CONDITIONS, list(SENSOR_TYPES)),
|
||||
config_entry.data.get(CONF_UNIT_SYSTEM))
|
||||
hass.loop.create_task(ambient.ws_connect())
|
||||
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient
|
||||
except WebsocketConnectionError as err:
|
||||
_LOGGER.error('Config entry failed: %s', err)
|
||||
raise ConfigEntryNotReady
|
||||
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, ambient.client.websocket.disconnect())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass, config_entry):
|
||||
"""Unload an Ambient PWS config entry."""
|
||||
ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||
hass.async_create_task(ambient.ws_disconnect())
|
||||
|
||||
await hass.config_entries.async_forward_entry_unload(
|
||||
config_entry, 'sensor')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AmbientStation:
|
||||
"""Define a class to handle the Ambient websocket."""
|
||||
|
||||
def __init__(
|
||||
self, hass, config_entry, client, monitored_conditions,
|
||||
unit_system):
|
||||
"""Initialize."""
|
||||
self._config_entry = config_entry
|
||||
self._hass = hass
|
||||
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
|
||||
self.client = client
|
||||
self.monitored_conditions = monitored_conditions
|
||||
self.stations = {}
|
||||
self.unit_system = unit_system
|
||||
|
||||
async def ws_connect(self):
|
||||
"""Register handlers and connect to the websocket."""
|
||||
from aioambient.errors import WebsocketError
|
||||
|
||||
def on_connect():
|
||||
"""Define a handler to fire when the websocket is connected."""
|
||||
_LOGGER.info('Connected to websocket')
|
||||
|
||||
def on_data(data):
|
||||
"""Define a handler to fire when the data is received."""
|
||||
mac_address = data['macAddress']
|
||||
if data != self.stations[mac_address][ATTR_LAST_DATA]:
|
||||
_LOGGER.debug('New data received: %s', data)
|
||||
self.stations[mac_address][ATTR_LAST_DATA] = data
|
||||
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||
|
||||
def on_disconnect():
|
||||
"""Define a handler to fire when the websocket is disconnected."""
|
||||
_LOGGER.info('Disconnected from websocket')
|
||||
|
||||
def on_subscribed(data):
|
||||
"""Define a handler to fire when the subscription is set."""
|
||||
for station in data['devices']:
|
||||
if station['macAddress'] in self.stations:
|
||||
continue
|
||||
|
||||
_LOGGER.debug('New station subscription: %s', data)
|
||||
|
||||
self.stations[station['macAddress']] = {
|
||||
ATTR_LAST_DATA: station['lastData'],
|
||||
ATTR_LOCATION: station['info']['location'],
|
||||
ATTR_NAME: station['info']['name'],
|
||||
}
|
||||
|
||||
self._hass.async_create_task(
|
||||
self._hass.config_entries.async_forward_entry_setup(
|
||||
self._config_entry, 'sensor'))
|
||||
|
||||
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
|
||||
|
||||
self.client.websocket.on_connect(on_connect)
|
||||
self.client.websocket.on_data(on_data)
|
||||
self.client.websocket.on_disconnect(on_disconnect)
|
||||
self.client.websocket.on_subscribed(on_subscribed)
|
||||
|
||||
try:
|
||||
await self.client.websocket.connect()
|
||||
except WebsocketError as err:
|
||||
_LOGGER.error("Error with the websocket connection: %s", err)
|
||||
|
||||
self._ws_reconnect_delay = min(
|
||||
2 * self._ws_reconnect_delay, 480)
|
||||
|
||||
async_call_later(
|
||||
self._hass, self._ws_reconnect_delay, self.ws_connect)
|
||||
|
||||
async def ws_disconnect(self):
|
||||
"""Disconnect from the websocket."""
|
||||
await self.client.websocket.disconnect()
|
||||
72
homeassistant/components/ambient_station/config_flow.py
Normal file
72
homeassistant/components/ambient_station/config_flow.py
Normal file
@@ -0,0 +1,72 @@
|
||||
"""Config flow to configure the Ambient PWS component."""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_APP_KEY, DOMAIN
|
||||
|
||||
|
||||
@callback
|
||||
def configured_instances(hass):
|
||||
"""Return a set of configured Ambient PWS instances."""
|
||||
return set(
|
||||
entry.data[CONF_APP_KEY]
|
||||
for entry in hass.config_entries.async_entries(DOMAIN))
|
||||
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class AmbientStationFlowHandler(config_entries.ConfigFlow):
|
||||
"""Handle an Ambient PWS config flow."""
|
||||
|
||||
VERSION = 1
|
||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
|
||||
|
||||
async def _show_form(self, errors=None):
|
||||
"""Show the form to the user."""
|
||||
data_schema = vol.Schema({
|
||||
vol.Required(CONF_API_KEY): str,
|
||||
vol.Required(CONF_APP_KEY): str,
|
||||
})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='user',
|
||||
data_schema=data_schema,
|
||||
errors=errors if errors else {},
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_config):
|
||||
"""Import a config entry from configuration.yaml."""
|
||||
return await self.async_step_user(import_config)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle the start of the config flow."""
|
||||
from aioambient import Client
|
||||
from aioambient.errors import AmbientError
|
||||
|
||||
if not user_input:
|
||||
return await self._show_form()
|
||||
|
||||
if user_input[CONF_APP_KEY] in configured_instances(self.hass):
|
||||
return await self._show_form({CONF_APP_KEY: 'identifier_exists'})
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||
client = Client(
|
||||
user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session)
|
||||
|
||||
try:
|
||||
devices = await client.api.get_devices()
|
||||
except AmbientError:
|
||||
return await self._show_form({'base': 'invalid_key'})
|
||||
|
||||
if not devices:
|
||||
return await self._show_form({'base': 'no_devices'})
|
||||
|
||||
# The Application Key (which identifies each config entry) is too long
|
||||
# to show nicely in the UI, so we take the first 12 characters (similar
|
||||
# to how GitHub does it):
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_APP_KEY][:12], data=user_input)
|
||||
13
homeassistant/components/ambient_station/const.py
Normal file
13
homeassistant/components/ambient_station/const.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Define constants for the Ambient PWS component."""
|
||||
DOMAIN = 'ambient_station'
|
||||
|
||||
ATTR_LAST_DATA = 'last_data'
|
||||
|
||||
CONF_APP_KEY = 'app_key'
|
||||
|
||||
DATA_CLIENT = 'data_client'
|
||||
|
||||
TOPIC_UPDATE = 'update'
|
||||
|
||||
UNITS_SI = 'si'
|
||||
UNITS_US = 'us'
|
||||
115
homeassistant/components/ambient_station/sensor.py
Normal file
115
homeassistant/components/ambient_station/sensor.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Support for Ambient Weather Station Service.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.ambient_station/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.ambient_station import SENSOR_TYPES
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_LAST_DATA, DATA_CLIENT, DOMAIN, TOPIC_UPDATE, UNITS_SI, UNITS_US)
|
||||
|
||||
DEPENDENCIES = ['ambient_station']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UNIT_SYSTEM = {UNITS_US: 0, UNITS_SI: 1}
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up an Ambient PWS sensor based on existing config."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up an Ambient PWS sensor based on a config entry."""
|
||||
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
|
||||
if ambient.unit_system:
|
||||
sys_units = ambient.unit_system
|
||||
elif hass.config.units.is_metric:
|
||||
sys_units = UNITS_SI
|
||||
else:
|
||||
sys_units = UNITS_US
|
||||
|
||||
sensor_list = []
|
||||
for mac_address, station in ambient.stations.items():
|
||||
for condition in ambient.monitored_conditions:
|
||||
name, unit = SENSOR_TYPES[condition]
|
||||
if isinstance(unit, list):
|
||||
unit = unit[UNIT_SYSTEM[sys_units]]
|
||||
|
||||
sensor_list.append(
|
||||
AmbientWeatherSensor(
|
||||
ambient, mac_address, station[ATTR_NAME], condition, name,
|
||||
unit))
|
||||
|
||||
async_add_entities(sensor_list, True)
|
||||
|
||||
|
||||
class AmbientWeatherSensor(Entity):
|
||||
"""Define an Ambient sensor."""
|
||||
|
||||
def __init__(
|
||||
self, ambient, mac_address, station_name, sensor_type, sensor_name,
|
||||
units):
|
||||
"""Initialize the sensor."""
|
||||
self._ambient = ambient
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
self._mac_address = mac_address
|
||||
self._sensor_name = sensor_name
|
||||
self._sensor_type = sensor_type
|
||||
self._state = None
|
||||
self._station_name = station_name
|
||||
self._units = units
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return '{0}_{1}'.format(self._station_name, self._sensor_name)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Disable polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement."""
|
||||
return self._units
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique, unchanging string that represents this sensor."""
|
||||
return '{0}_{1}'.format(self._mac_address, self._sensor_name)
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
@callback
|
||||
def update():
|
||||
"""Update the state."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||
self.hass, TOPIC_UPDATE, update)
|
||||
|
||||
async def async_will_remove_from_hass(self):
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_update(self):
|
||||
"""Fetch new state data for the sensor."""
|
||||
self._state = self._ambient.stations[
|
||||
self._mac_address][ATTR_LAST_DATA].get(self._sensor_type)
|
||||
19
homeassistant/components/ambient_station/strings.json
Normal file
19
homeassistant/components/ambient_station/strings.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"title": "Ambient PWS",
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Fill in your information",
|
||||
"data": {
|
||||
"api_key": "API Key",
|
||||
"app_key": "Application Key"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"identifier_exists": "Application Key and/or API Key already registered",
|
||||
"invalid_key": "Invalid API Key and/or Application Key",
|
||||
"no_devices": "No devices found in account"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +123,9 @@ ICON_MAP = {
|
||||
'whitebalance_lock': 'mdi:white-balance-auto'
|
||||
}
|
||||
|
||||
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
|
||||
'overlay', 'torch', 'whitebalance_lock', 'video_recording']
|
||||
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active',
|
||||
'motion_detect', 'night_vision', 'overlay',
|
||||
'torch', 'whitebalance_lock', 'video_recording']
|
||||
|
||||
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
|
||||
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
|
||||
REQUIREMENTS = ['aioasuswrt==1.1.17']
|
||||
REQUIREMENTS = ['aioasuswrt==1.1.20']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,6 +26,8 @@ CONF_SSH_KEY = 'ssh_key'
|
||||
CONF_REQUIRE_IP = 'require_ip'
|
||||
DEFAULT_SSH_PORT = 22
|
||||
SECRET_GROUP = 'Password or SSH Key'
|
||||
CONF_SENSORS = 'sensors'
|
||||
SENSOR_TYPES = ['upload_speed', 'download_speed', 'download', 'upload']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
@@ -37,7 +39,9 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean,
|
||||
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
|
||||
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
|
||||
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
|
||||
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile,
|
||||
vol.Optional(CONF_SENSORS): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -62,7 +66,8 @@ async def async_setup(hass, config):
|
||||
hass.data[DATA_ASUSWRT] = api
|
||||
|
||||
hass.async_create_task(async_load_platform(
|
||||
hass, 'sensor', DOMAIN, {}, config))
|
||||
hass, 'sensor', DOMAIN, config[DOMAIN].get(CONF_SENSORS), config))
|
||||
hass.async_create_task(async_load_platform(
|
||||
hass, 'device_tracker', DOMAIN, {}, config))
|
||||
|
||||
return True
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Bitte w\u00e4hle einen der Benachrichtigungsdienste:",
|
||||
"description": "Bitte w\u00e4hlen Sie einen der Benachrichtigungsdienste:",
|
||||
"title": "Einmal Passwort f\u00fcr Notify einrichten"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
|
||||
"description": "Ein Einmal-Passwort wurde per **notify.{notify_service}** gesendet. Bitte geben Sie es unten ein:",
|
||||
"title": "\u00dcberpr\u00fcfe das Setup"
|
||||
}
|
||||
},
|
||||
|
||||
7
homeassistant/components/auth/.translations/et.json
Normal file
7
homeassistant/components/auth/.translations/et.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:",
|
||||
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 us\u0142ug\u0119 powiadamiania:",
|
||||
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
||||
},
|
||||
"setup": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistenta to\u010dna."
|
||||
"invalid_code": "Neveljavna koda, prosimo, poskusite znova. \u010ce dobite to sporo\u010dilo ve\u010dkrat, prosimo poskrbite, da bo ura va\u0161ega Home Assistanta to\u010dna."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
"notify": {
|
||||
"error": {
|
||||
"invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437."
|
||||
},
|
||||
"step": {
|
||||
"setup": {
|
||||
"title": "\u041f\u0435\u0440\u0435\u0432\u0456\u0440\u0442\u0435 \u043d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,8 +375,6 @@ def _async_get_action(hass, config, name):
|
||||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
hass.components.logbook.async_log_entry(
|
||||
name, 'has been triggered', DOMAIN, entity_id)
|
||||
|
||||
try:
|
||||
await script_obj.async_run(variables, context)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
Offer geo location automation rules.
|
||||
Offer geolocation automation rules.
|
||||
|
||||
For more details about this automation trigger, please refer to the
|
||||
documentation at
|
||||
https://home-assistant.io/docs/automation/trigger/#geo-location-trigger
|
||||
https://home-assistant.io/docs/automation/trigger/#geolocation-trigger
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
@@ -15,19 +15,23 @@ import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
CONF_ENCODING = 'encoding'
|
||||
CONF_TOPIC = 'topic'
|
||||
DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): mqtt.DOMAIN,
|
||||
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
||||
vol.Optional(CONF_ENCODING, default=DEFAULT_ENCODING): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
topic = config.get(CONF_TOPIC)
|
||||
topic = config[CONF_TOPIC]
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
encoding = config[CONF_ENCODING] or None
|
||||
|
||||
@callback
|
||||
def mqtt_automation_listener(msg_topic, msg_payload, qos):
|
||||
@@ -50,5 +54,5 @@ async def async_trigger(hass, config, action, automation_info):
|
||||
})
|
||||
|
||||
remove = await mqtt.async_subscribe(
|
||||
hass, topic, mqtt_automation_listener)
|
||||
hass, topic, mqtt_automation_listener, encoding=encoding)
|
||||
return remove
|
||||
|
||||
@@ -13,30 +13,18 @@ from homeassistant.const import CONF_AT, CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
CONF_HOURS = 'hours'
|
||||
CONF_MINUTES = 'minutes'
|
||||
CONF_SECONDS = 'seconds'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'time',
|
||||
CONF_AT: cv.time,
|
||||
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
|
||||
vol.Required(CONF_AT): cv.time,
|
||||
})
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AT in config:
|
||||
at_time = config.get(CONF_AT)
|
||||
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
|
||||
else:
|
||||
hours = config.get(CONF_HOURS)
|
||||
minutes = config.get(CONF_MINUTES)
|
||||
seconds = config.get(CONF_SECONDS)
|
||||
at_time = config.get(CONF_AT)
|
||||
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
|
||||
|
||||
@callback
|
||||
def time_automation_listener(now):
|
||||
|
||||
53
homeassistant/components/automation/time_pattern.py
Normal file
53
homeassistant/components/automation/time_pattern.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Offer time listening automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#time-trigger
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
CONF_HOURS = 'hours'
|
||||
CONF_MINUTES = 'minutes'
|
||||
CONF_SECONDS = 'seconds'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'time_pattern',
|
||||
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS))
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
hours = config.get(CONF_HOURS)
|
||||
minutes = config.get(CONF_MINUTES)
|
||||
seconds = config.get(CONF_SECONDS)
|
||||
|
||||
# If larger units are specified, default the smaller units to zero
|
||||
if minutes is None and hours is not None:
|
||||
minutes = 0
|
||||
if seconds is None and minutes is not None:
|
||||
seconds = 0
|
||||
|
||||
@callback
|
||||
def time_automation_listener(now):
|
||||
"""Listen for time changes and calls action."""
|
||||
hass.async_run_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'time_pattern',
|
||||
'now': now,
|
||||
},
|
||||
})
|
||||
|
||||
return async_track_time_change(hass, time_automation_listener,
|
||||
hour=hours, minute=minutes, second=seconds)
|
||||
@@ -50,9 +50,7 @@ DEVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: DEVICE_SCHEMA,
|
||||
}),
|
||||
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SERVICE_VAPIX_CALL = 'vapix_call'
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
"""
|
||||
Support for deCONZ binary sensor.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.deconz/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.deconz.const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DECONZ_REACHABLE,
|
||||
DOMAIN as DECONZ_DOMAIN)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Old way of setting up deCONZ binary sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ binary sensor."""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_sensor(sensors):
|
||||
"""Add binary sensor from deCONZ."""
|
||||
from pydeconz.sensor import DECONZ_BINARY_SENSOR
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
if sensor.type in DECONZ_BINARY_SENSOR and \
|
||||
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
|
||||
entities.append(DeconzBinarySensor(sensor, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
||||
class DeconzBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a binary sensor."""
|
||||
|
||||
def __init__(self, sensor, gateway):
|
||||
"""Set up sensor and add update callback to get data from websocket."""
|
||||
self._sensor = sensor
|
||||
self.gateway = gateway
|
||||
self.unsub_dispatcher = None
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe sensors events."""
|
||||
self._sensor.register_async_callback(self.async_update_callback)
|
||||
self.gateway.deconz_ids[self.entity_id] = self._sensor.deconz_id
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, DECONZ_REACHABLE, self.async_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect sensor object when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
self._sensor.remove_callback(self.async_update_callback)
|
||||
self._sensor = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the sensor's state.
|
||||
|
||||
If reason is that state is updated,
|
||||
or reachable has changed or battery has changed.
|
||||
"""
|
||||
if reason['state'] or \
|
||||
'reachable' in reason['attr'] or \
|
||||
'battery' in reason['attr'] or \
|
||||
'on' in reason['attr']:
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
return self._sensor.is_tripped
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._sensor.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this sensor."""
|
||||
return self._sensor.uniqueid
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return self._sensor.sensor_class
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._sensor.sensor_icon
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if sensor is available."""
|
||||
return self.gateway.available and self._sensor.reachable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
from pydeconz.sensor import PRESENCE
|
||||
attr = {}
|
||||
if self._sensor.battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
|
||||
if self._sensor.on is not None:
|
||||
attr[ATTR_ON] = self._sensor.on
|
||||
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
|
||||
attr[ATTR_DARK] = self._sensor.dark
|
||||
return attr
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._sensor.uniqueid is None or
|
||||
self._sensor.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._sensor.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.gateway.api.config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
'sw_version': self._sensor.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, ENTITY_ID_FORMAT)
|
||||
from homeassistant.components.fibaro import (
|
||||
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
|
||||
FIBARO_DEVICES, FibaroDevice)
|
||||
from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON)
|
||||
|
||||
DEPENDENCIES = ['fibaro']
|
||||
@@ -33,17 +33,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
return
|
||||
|
||||
add_entities(
|
||||
[FibaroBinarySensor(device, hass.data[FIBARO_CONTROLLER])
|
||||
[FibaroBinarySensor(device)
|
||||
for device in hass.data[FIBARO_DEVICES]['binary_sensor']], True)
|
||||
|
||||
|
||||
class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
|
||||
"""Representation of a Fibaro Binary Sensor."""
|
||||
|
||||
def __init__(self, fibaro_device, controller):
|
||||
def __init__(self, fibaro_device):
|
||||
"""Initialize the binary_sensor."""
|
||||
self._state = None
|
||||
super().__init__(fibaro_device, controller)
|
||||
super().__init__(fibaro_device)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||
stype = None
|
||||
devconf = fibaro_device.device_config
|
||||
|
||||
@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
from homeassistant.components.hive import DATA_HIVE, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
@@ -35,9 +35,24 @@ class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
self.attributes = {}
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID of entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {
|
||||
'identifiers': {
|
||||
(DOMAIN, self.unique_id)
|
||||
},
|
||||
'name': self.name
|
||||
}
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
|
||||
@@ -15,8 +15,6 @@ DEPENDENCIES = ['homematicip_cloud']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STATE_SMOKE_OFF = 'IDLE_OFF'
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
@@ -65,7 +63,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
|
||||
return True
|
||||
if self._device.windowState is None:
|
||||
return None
|
||||
return self._device.windowState == WindowState.OPEN
|
||||
return self._device.windowState != WindowState.CLOSED
|
||||
|
||||
|
||||
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
|
||||
@@ -95,7 +93,9 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if smoke is detected."""
|
||||
return self._device.smokeDetectorAlarmType != STATE_SMOKE_OFF
|
||||
from homematicip.base.enums import SmokeDetectorAlarmType
|
||||
return (self._device.smokeDetectorAlarmType
|
||||
!= SmokeDetectorAlarmType.IDLE_OFF)
|
||||
|
||||
|
||||
class HomematicipWaterDetector(HomematicipGenericDevice, BinarySensorDevice):
|
||||
|
||||
@@ -8,7 +8,6 @@ import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.maxcube import DATA_KEY
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,7 +39,7 @@ class MaxCubeShutter(BinarySensorDevice):
|
||||
self._sensor_type = 'window'
|
||||
self._rf_address = rf_address
|
||||
self._cubehandle = handler
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
||||
@@ -41,7 +41,7 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
|
||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.util import utcnow
|
||||
|
||||
REQUIREMENTS = ['numpy==1.15.4']
|
||||
REQUIREMENTS = ['numpy==1.16.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,7 +51,7 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
|
||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.const import CONF_NAME, WEEKDAYS
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['holidays==0.9.8']
|
||||
REQUIREMENTS = ['holidays==0.9.9']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,6 +26,7 @@ ALL_COUNTRIES = [
|
||||
'Canada', 'CA', 'Colombia', 'CO', 'Croatia', 'HR', 'Czech', 'CZ',
|
||||
'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR',
|
||||
'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU',
|
||||
'Honduras', 'HUD',
|
||||
'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP',
|
||||
'Mexico', 'MX', 'Netherlands', 'NL', 'NewZealand', 'NZ',
|
||||
'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
|
||||
|
||||
@@ -107,7 +107,7 @@ class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice):
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor state."""
|
||||
_LOGGER.debug('Updating xiaomi sensor by polling')
|
||||
_LOGGER.debug('Updating xiaomi sensor (%s) by polling', self._sid)
|
||||
self._get_from_hub(self._sid)
|
||||
|
||||
|
||||
@@ -178,7 +178,28 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
"""Parse data sent by gateway.
|
||||
|
||||
Polling (proto v1, firmware version 1.4.1_159.0143)
|
||||
|
||||
>> { "cmd":"read","sid":"158..."}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'read_ack', 'data': '{"voltage":3005}'}
|
||||
|
||||
Multicast messages (proto v1, firmware version 1.4.1_159.0143)
|
||||
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"status":"motion"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"120"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"180"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"300"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'heartbeat', 'data': '{"voltage":3005}'}
|
||||
|
||||
"""
|
||||
if raw_data['cmd'] == 'heartbeat':
|
||||
_LOGGER.debug(
|
||||
'Skipping heartbeat of the motion sensor. '
|
||||
@@ -187,8 +208,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
'11631#issuecomment-357507744).')
|
||||
return
|
||||
|
||||
self._should_poll = False
|
||||
if NO_MOTION in data: # handle push from the hub
|
||||
if NO_MOTION in data:
|
||||
self._no_motion_since = data[NO_MOTION]
|
||||
self._state = False
|
||||
return True
|
||||
@@ -203,26 +223,20 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
self._unsub_set_no_motion()
|
||||
self._unsub_set_no_motion = async_call_later(
|
||||
self._hass,
|
||||
180,
|
||||
120,
|
||||
self._async_set_no_motion
|
||||
)
|
||||
else:
|
||||
self._should_poll = True
|
||||
if self.entity_id is not None:
|
||||
self._hass.bus.fire('xiaomi_aqara.motion', {
|
||||
'entity_id': self.entity_id
|
||||
})
|
||||
|
||||
if self.entity_id is not None:
|
||||
self._hass.bus.fire('xiaomi_aqara.motion', {
|
||||
'entity_id': self.entity_id
|
||||
})
|
||||
|
||||
self._no_motion_since = 0
|
||||
if self._state:
|
||||
return False
|
||||
self._state = True
|
||||
return True
|
||||
if value == NO_MOTION:
|
||||
if not self._state:
|
||||
return False
|
||||
self._state = False
|
||||
return True
|
||||
|
||||
|
||||
class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
|
||||
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
|
||||
|
||||
REQUIREMENTS = ['blinkpy==0.11.0']
|
||||
REQUIREMENTS = ['blinkpy==0.12.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['libpyfoscam==1.0']
|
||||
REQUIREMENTS = ['pyfoscam==1.2']
|
||||
|
||||
CONF_IP = 'ip'
|
||||
|
||||
@@ -43,7 +43,7 @@ class FoscamCam(Camera):
|
||||
|
||||
def __init__(self, device_info):
|
||||
"""Initialize a Foscam camera."""
|
||||
from libpyfoscam import FoscamCamera
|
||||
from foscam import FoscamCamera
|
||||
|
||||
super(FoscamCam, self).__init__()
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
"""
|
||||
Camera that loads a picture from an MQTT topic.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.mqtt/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.components import mqtt, camera
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_TOPIC = 'topic'
|
||||
CONF_UNIQUE_ID = 'unique_id'
|
||||
DEFAULT_NAME = 'MQTT Camera'
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_entities, discovery_info=None):
|
||||
"""Set up MQTT camera through configuration.yaml."""
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up MQTT camera dynamically through MQTT discovery."""
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add a MQTT camera."""
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format(camera.DOMAIN, 'mqtt'),
|
||||
async_discover)
|
||||
|
||||
|
||||
async def _async_setup_entity(hass, config, async_add_entities):
|
||||
"""Set up the MQTT Camera."""
|
||||
async_add_entities([MqttCamera(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config.get(CONF_TOPIC)
|
||||
)])
|
||||
|
||||
|
||||
class MqttCamera(Camera):
|
||||
"""representation of a MQTT camera."""
|
||||
|
||||
def __init__(self, name, unique_id, topic):
|
||||
"""Initialize the MQTT Camera."""
|
||||
super().__init__()
|
||||
|
||||
self._name = name
|
||||
self._unique_id = unique_id
|
||||
self._topic = topic
|
||||
self._qos = 0
|
||||
self._last_image = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
"""Return image response."""
|
||||
return self._last_image
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe MQTT events."""
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
"""Handle new MQTT messages."""
|
||||
self._last_image = payload
|
||||
|
||||
await mqtt.async_subscribe(
|
||||
self.hass, self._topic, message_received, self._qos, None)
|
||||
@@ -7,6 +7,7 @@ https://www.home-assistant.io/components/camera.proxy/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
|
||||
@@ -18,7 +19,7 @@ from homeassistant.util.async_ import run_coroutine_threadsafe
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.camera import async_get_still_stream
|
||||
|
||||
REQUIREMENTS = ['pillow==5.3.0']
|
||||
REQUIREMENTS = ['pillow==5.4.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -206,7 +207,7 @@ class ProxyCamera(Camera):
|
||||
self._cache_images = bool(
|
||||
config.get(CONF_IMAGE_REFRESH_RATE)
|
||||
or config.get(CONF_CACHE_IMAGES))
|
||||
self._last_image_time = 0
|
||||
self._last_image_time = dt_util.utc_from_timestamp(0)
|
||||
self._last_image = None
|
||||
self._headers = (
|
||||
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
|
||||
@@ -223,7 +224,8 @@ class ProxyCamera(Camera):
|
||||
now = dt_util.utcnow()
|
||||
|
||||
if (self._image_refresh_rate and
|
||||
now < self._last_image_time + self._image_refresh_rate):
|
||||
now < self._last_image_time +
|
||||
timedelta(seconds=self._image_refresh_rate)):
|
||||
return self._last_image
|
||||
|
||||
self._last_image_time = now
|
||||
|
||||
@@ -107,7 +107,7 @@ class XiaomiCamera(Camera):
|
||||
_LOGGER.warning("There don't appear to be any folders")
|
||||
return False
|
||||
|
||||
first_dir = dirs[-1]
|
||||
first_dir = latest_dir = dirs[-1]
|
||||
try:
|
||||
ftp.cwd(first_dir)
|
||||
except error_perm as exc:
|
||||
|
||||
10
homeassistant/components/cast/.translations/et.json
Normal file
10
homeassistant/components/cast/.translations/et.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
|
||||
STATE_ON, STATE_OFF, TEMP_CELSIUS, PRECISION_WHOLE,
|
||||
PRECISION_TENTHS)
|
||||
|
||||
DEFAULT_MIN_TEMP = 7
|
||||
@@ -208,7 +208,7 @@ class ClimateDevice(Entity):
|
||||
return self.current_operation
|
||||
if self.is_on:
|
||||
return STATE_ON
|
||||
return STATE_UNKNOWN
|
||||
return None
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
from homeassistant.components.hive import DATA_HIVE, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
|
||||
@@ -44,6 +44,7 @@ class HiveClimateEntity(ClimateDevice):
|
||||
self.attributes = {}
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
|
||||
|
||||
if self.device_type == "Heating":
|
||||
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
@@ -52,6 +53,21 @@ class HiveClimateEntity(ClimateDevice):
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID of entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {
|
||||
'identifiers': {
|
||||
(DOMAIN, self.unique_id)
|
||||
},
|
||||
'name': self.name
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
REQUIREMENTS = ['millheater==0.3.3']
|
||||
REQUIREMENTS = ['millheater==0.3.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.components.climate import (
|
||||
SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE)
|
||||
from homeassistant.const import (
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
|
||||
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['nest']
|
||||
@@ -163,7 +163,7 @@ class NestThermostat(ClimateDevice):
|
||||
return self._mode
|
||||
if self._mode == NEST_MODE_HEAT_COOL:
|
||||
return STATE_AUTO
|
||||
return STATE_UNKNOWN
|
||||
return None
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
|
||||
@@ -219,6 +219,11 @@ class RadioThermostat(ClimateDevice):
|
||||
"""Return true if away mode is on."""
|
||||
return self._away
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if on."""
|
||||
return self._tstate != STATE_IDLE
|
||||
|
||||
def update(self):
|
||||
"""Update and validate the data from the thermostat."""
|
||||
# Radio thermostats are very slow, and sometimes don't respond
|
||||
|
||||
@@ -19,7 +19,8 @@ from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_ON_OFF)
|
||||
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
|
||||
STATE_AUTO)
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -57,6 +58,16 @@ FIELD_TO_FLAG = {
|
||||
'on': SUPPORT_ON_OFF,
|
||||
}
|
||||
|
||||
SENSIBO_TO_HA = {
|
||||
"cool": STATE_COOL,
|
||||
"heat": STATE_HEAT,
|
||||
"fan": STATE_FAN_ONLY,
|
||||
"auto": STATE_AUTO,
|
||||
"dry": STATE_DRY
|
||||
}
|
||||
|
||||
HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
@@ -129,9 +140,10 @@ class SensiboClimate(ClimateDevice):
|
||||
self._ac_states = data['acState']
|
||||
self._status = data['connectionStatus']['isAlive']
|
||||
capabilities = data['remoteCapabilities']
|
||||
self._operations = sorted(capabilities['modes'].keys())
|
||||
self._current_capabilities = capabilities[
|
||||
'modes'][self.current_operation]
|
||||
self._operations = [SENSIBO_TO_HA[mode] for mode
|
||||
in capabilities['modes']]
|
||||
self._current_capabilities = \
|
||||
capabilities['modes'][self._ac_states['mode']]
|
||||
temperature_unit_key = data.get('temperatureUnit') or \
|
||||
self._ac_states.get('temperatureUnit')
|
||||
if temperature_unit_key:
|
||||
@@ -186,7 +198,7 @@ class SensiboClimate(ClimateDevice):
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._ac_states['mode']
|
||||
return SENSIBO_TO_HA.get(self._ac_states['mode'])
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
@@ -293,7 +305,8 @@ class SensiboClimate(ClimateDevice):
|
||||
"""Set new target operation mode."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'mode', operation_mode, self._ac_states)
|
||||
self._id, 'mode', HA_TO_SENSIBO[operation_mode],
|
||||
self._ac_states)
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import pprint
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from aiohttp import hdrs, client_exceptions, WSMsgType
|
||||
@@ -107,9 +108,11 @@ class CloudIoT:
|
||||
self.tries += 1
|
||||
|
||||
try:
|
||||
# Sleep 2^tries seconds between retries
|
||||
self.retry_task = hass.async_create_task(asyncio.sleep(
|
||||
2**min(9, self.tries), loop=hass.loop))
|
||||
# Sleep 2^tries + 0…tries*3 seconds between retries
|
||||
self.retry_task = hass.async_create_task(
|
||||
asyncio.sleep(2**min(9, self.tries) +
|
||||
random.randint(0, self.tries * 3),
|
||||
loop=hass.loop))
|
||||
yield from self.retry_task
|
||||
self.retry_task = None
|
||||
except asyncio.CancelledError:
|
||||
@@ -313,15 +316,20 @@ def async_handle_google_actions(hass, cloud, payload):
|
||||
|
||||
|
||||
@HANDLERS.register('cloud')
|
||||
@asyncio.coroutine
|
||||
def async_handle_cloud(hass, cloud, payload):
|
||||
async def async_handle_cloud(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for cloud component."""
|
||||
action = payload['action']
|
||||
|
||||
if action == 'logout':
|
||||
yield from cloud.logout()
|
||||
# Log out of Home Assistant Cloud
|
||||
await cloud.logout()
|
||||
_LOGGER.error("You have been logged out from Home Assistant cloud: %s",
|
||||
payload['reason'])
|
||||
elif action == 'refresh_auth':
|
||||
# Refresh the auth token between now and payload['seconds']
|
||||
hass.helpers.event.async_call_later(
|
||||
random.randint(0, payload['seconds']),
|
||||
lambda now: auth_api.check_token(cloud))
|
||||
else:
|
||||
_LOGGER.warning("Received unknown cloud action: %s", action)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from homeassistant.util.yaml import load_yaml, dump
|
||||
DOMAIN = 'config'
|
||||
DEPENDENCIES = ['http']
|
||||
SECTIONS = (
|
||||
'area_registry',
|
||||
'auth',
|
||||
'auth_provider_homeassistant',
|
||||
'automation',
|
||||
|
||||
126
homeassistant/components/config/area_registry.py
Normal file
126
homeassistant/components/config/area_registry.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""HTTP views to interact with the area registry."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.websocket_api.decorators import (
|
||||
async_response, require_admin)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.area_registry import async_get_registry
|
||||
|
||||
|
||||
DEPENDENCIES = ['websocket_api']
|
||||
|
||||
WS_TYPE_LIST = 'config/area_registry/list'
|
||||
SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_LIST,
|
||||
})
|
||||
|
||||
WS_TYPE_CREATE = 'config/area_registry/create'
|
||||
SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_CREATE,
|
||||
vol.Required('name'): str,
|
||||
})
|
||||
|
||||
WS_TYPE_DELETE = 'config/area_registry/delete'
|
||||
SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_DELETE,
|
||||
vol.Required('area_id'): str,
|
||||
})
|
||||
|
||||
WS_TYPE_UPDATE = 'config/area_registry/update'
|
||||
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_UPDATE,
|
||||
vol.Required('area_id'): str,
|
||||
vol.Required('name'): str,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup(hass):
|
||||
"""Enable the Area Registry views."""
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_LIST, websocket_list_areas, SCHEMA_WS_LIST
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_CREATE, websocket_create_area, SCHEMA_WS_CREATE
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_DELETE, websocket_delete_area, SCHEMA_WS_DELETE
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_UPDATE, websocket_update_area, SCHEMA_WS_UPDATE
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@async_response
|
||||
async def websocket_list_areas(hass, connection, msg):
|
||||
"""Handle list areas command."""
|
||||
registry = await async_get_registry(hass)
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], [{
|
||||
'name': entry.name,
|
||||
'area_id': entry.id,
|
||||
} for entry in registry.async_list_areas()]
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_create_area(hass, connection, msg):
|
||||
"""Create area command."""
|
||||
registry = await async_get_registry(hass)
|
||||
try:
|
||||
entry = registry.async_create(msg['name'])
|
||||
except ValueError as err:
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'invalid_info', str(err)
|
||||
))
|
||||
else:
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], _entry_dict(entry)
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_delete_area(hass, connection, msg):
|
||||
"""Delete area command."""
|
||||
registry = await async_get_registry(hass)
|
||||
|
||||
try:
|
||||
await registry.async_delete(msg['area_id'])
|
||||
except KeyError:
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'invalid_info', "Area ID doesn't exist"
|
||||
))
|
||||
else:
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], 'success'
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_update_area(hass, connection, msg):
|
||||
"""Handle update area websocket command."""
|
||||
registry = await async_get_registry(hass)
|
||||
|
||||
try:
|
||||
entry = registry.async_update(msg['area_id'], msg['name'])
|
||||
except ValueError as err:
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'invalid_info', str(err)
|
||||
))
|
||||
else:
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], _entry_dict(entry)
|
||||
))
|
||||
|
||||
|
||||
@callback
|
||||
def _entry_dict(entry):
|
||||
"""Convert entry to API format."""
|
||||
return {
|
||||
'area_id': entry.id,
|
||||
'name': entry.name
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
"""HTTP views to interact with the device registry."""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers.device_registry import async_get_registry
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.websocket_api.decorators import (
|
||||
async_response, require_admin)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import async_get_registry
|
||||
|
||||
DEPENDENCIES = ['websocket_api']
|
||||
|
||||
@@ -11,29 +14,60 @@ SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_LIST,
|
||||
})
|
||||
|
||||
WS_TYPE_UPDATE = 'config/device_registry/update'
|
||||
SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_UPDATE,
|
||||
vol.Required('device_id'): str,
|
||||
vol.Optional('area_id'): vol.Any(str, None),
|
||||
})
|
||||
|
||||
|
||||
async def async_setup(hass):
|
||||
"""Enable the Entity Registry views."""
|
||||
"""Enable the Device Registry views."""
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_LIST, websocket_list_devices,
|
||||
SCHEMA_WS_LIST
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_UPDATE, websocket_update_device, SCHEMA_WS_UPDATE
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@websocket_api.async_response
|
||||
@async_response
|
||||
async def websocket_list_devices(hass, connection, msg):
|
||||
"""Handle list devices command."""
|
||||
registry = await async_get_registry(hass)
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], [{
|
||||
'config_entries': list(entry.config_entries),
|
||||
'connections': list(entry.connections),
|
||||
'manufacturer': entry.manufacturer,
|
||||
'model': entry.model,
|
||||
'name': entry.name,
|
||||
'sw_version': entry.sw_version,
|
||||
'id': entry.id,
|
||||
'hub_device_id': entry.hub_device_id,
|
||||
} for entry in registry.devices.values()]
|
||||
msg['id'], [_entry_dict(entry) for entry in registry.devices.values()]
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_update_device(hass, connection, msg):
|
||||
"""Handle update area websocket command."""
|
||||
registry = await async_get_registry(hass)
|
||||
|
||||
entry = registry.async_update_device(
|
||||
msg['device_id'], area_id=msg['area_id'])
|
||||
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], _entry_dict(entry)
|
||||
))
|
||||
|
||||
|
||||
@callback
|
||||
def _entry_dict(entry):
|
||||
"""Convert entry to API format."""
|
||||
return {
|
||||
'config_entries': list(entry.config_entries),
|
||||
'connections': list(entry.connections),
|
||||
'manufacturer': entry.manufacturer,
|
||||
'model': entry.model,
|
||||
'name': entry.name,
|
||||
'sw_version': entry.sw_version,
|
||||
'id': entry.id,
|
||||
'hub_device_id': entry.hub_device_id,
|
||||
'area_id': entry.area_id,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers.entity_registry import async_get_registry
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
|
||||
from homeassistant.components.websocket_api.decorators import async_response
|
||||
from homeassistant.components.websocket_api.decorators import (
|
||||
async_response, require_admin)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['websocket_api']
|
||||
@@ -30,6 +31,12 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Optional('new_entity_id'): str,
|
||||
})
|
||||
|
||||
WS_TYPE_REMOVE = 'config/entity_registry/remove'
|
||||
SCHEMA_WS_REMOVE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_REMOVE,
|
||||
vol.Required('entity_id'): cv.entity_id
|
||||
})
|
||||
|
||||
|
||||
async def async_setup(hass):
|
||||
"""Enable the Entity Registry views."""
|
||||
@@ -45,6 +52,10 @@ async def async_setup(hass):
|
||||
WS_TYPE_UPDATE, websocket_update_entity,
|
||||
SCHEMA_WS_UPDATE
|
||||
)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_REMOVE, websocket_remove_entity,
|
||||
SCHEMA_WS_REMOVE
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -56,14 +67,7 @@ async def websocket_list_entities(hass, connection, msg):
|
||||
"""
|
||||
registry = await async_get_registry(hass)
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], [{
|
||||
'config_entry_id': entry.config_entry_id,
|
||||
'device_id': entry.device_id,
|
||||
'disabled_by': entry.disabled_by,
|
||||
'entity_id': entry.entity_id,
|
||||
'name': entry.name,
|
||||
'platform': entry.platform,
|
||||
} for entry in registry.entities.values()]
|
||||
msg['id'], [_entry_dict(entry) for entry in registry.entities.values()]
|
||||
))
|
||||
|
||||
|
||||
@@ -86,6 +90,7 @@ async def websocket_get_entity(hass, connection, msg):
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_update_entity(hass, connection, msg):
|
||||
"""Handle update entity websocket command.
|
||||
@@ -125,10 +130,32 @@ async def websocket_update_entity(hass, connection, msg):
|
||||
))
|
||||
|
||||
|
||||
@require_admin
|
||||
@async_response
|
||||
async def websocket_remove_entity(hass, connection, msg):
|
||||
"""Handle remove entity websocket command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
registry = await async_get_registry(hass)
|
||||
|
||||
if msg['entity_id'] not in registry.entities:
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], ERR_NOT_FOUND, 'Entity not found'))
|
||||
return
|
||||
|
||||
registry.async_remove(msg['entity_id'])
|
||||
connection.send_message(websocket_api.result_message(msg['id']))
|
||||
|
||||
|
||||
@callback
|
||||
def _entry_dict(entry):
|
||||
"""Convert entry to API format."""
|
||||
return {
|
||||
'config_entry_id': entry.config_entry_id,
|
||||
'device_id': entry.device_id,
|
||||
'disabled_by': entry.disabled_by,
|
||||
'entity_id': entry.entity_id,
|
||||
'name': entry.name
|
||||
'name': entry.name,
|
||||
'platform': entry.platform,
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ SERVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: vol.Any({
|
||||
DOMAIN: cv.schema_with_slug_keys(
|
||||
vol.Any({
|
||||
vol.Optional(CONF_ICON): cv.icon,
|
||||
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL):
|
||||
cv.positive_int,
|
||||
@@ -46,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_RESTORE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
|
||||
}, None)
|
||||
})
|
||||
)
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.const import (
|
||||
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
|
||||
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
|
||||
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN,
|
||||
STATE_CLOSED, STATE_UNKNOWN, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
|
||||
STATE_CLOSED, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -178,7 +178,7 @@ class CoverDevice(Entity):
|
||||
closed = self.is_closed
|
||||
|
||||
if closed is None:
|
||||
return STATE_UNKNOWN
|
||||
return None
|
||||
|
||||
return STATE_CLOSED if closed else STATE_OPEN
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION, ATTR_TILT_POSITION)
|
||||
from homeassistant.components.fibaro import (
|
||||
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
|
||||
FIBARO_DEVICES, FibaroDevice)
|
||||
|
||||
DEPENDENCIES = ['fibaro']
|
||||
|
||||
@@ -22,16 +22,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
return
|
||||
|
||||
add_entities(
|
||||
[FibaroCover(device, hass.data[FIBARO_CONTROLLER]) for
|
||||
[FibaroCover(device) for
|
||||
device in hass.data[FIBARO_DEVICES]['cover']], True)
|
||||
|
||||
|
||||
class FibaroCover(FibaroDevice, CoverDevice):
|
||||
"""Representation a Fibaro Cover."""
|
||||
|
||||
def __init__(self, fibaro_device, controller):
|
||||
def __init__(self, fibaro_device):
|
||||
"""Initialize the Vera device."""
|
||||
super().__init__(fibaro_device, controller)
|
||||
super().__init__(fibaro_device)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME,
|
||||
STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, CONF_COVERS)
|
||||
STATE_CLOSED, STATE_OPEN, CONF_COVERS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -47,7 +47,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class GaradgetCover(CoverDevice):
|
||||
self.obtained_token = False
|
||||
self._username = args['username']
|
||||
self._password = args['password']
|
||||
self._state = STATE_UNKNOWN
|
||||
self._state = None
|
||||
self.time_in_state = None
|
||||
self.signal = None
|
||||
self.sensor = None
|
||||
@@ -156,7 +156,7 @@ class GaradgetCover(CoverDevice):
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
if self._state == STATE_UNKNOWN:
|
||||
if self._state is None:
|
||||
return None
|
||||
return self._state == STATE_CLOSED
|
||||
|
||||
@@ -226,7 +226,7 @@ class GaradgetCover(CoverDevice):
|
||||
try:
|
||||
status = self._get_variable('doorStatus')
|
||||
_LOGGER.debug("Current Status: %s", status['status'])
|
||||
self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN)
|
||||
self._state = STATES_MAP.get(status['status'], None)
|
||||
self.time_in_state = status['time']
|
||||
self.signal = status['signal']
|
||||
self.sensor = status['sensor']
|
||||
|
||||
70
homeassistant/components/cover/homematicip_cloud.py
Normal file
70
homeassistant/components/cover/homematicip_cloud.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Support for HomematicIP Cloud cover devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.homematicip_cloud/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION, CoverDevice)
|
||||
from homeassistant.components.homematicip_cloud import (
|
||||
HMIPC_HAPID, HomematicipGenericDevice, DOMAIN as HMIPC_DOMAIN)
|
||||
|
||||
DEPENDENCIES = ['homematicip_cloud']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the HomematicIP Cloud cover devices."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the HomematicIP cover from a config entry."""
|
||||
from homematicip.aio.device import AsyncFullFlushShutter
|
||||
|
||||
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
|
||||
devices = []
|
||||
for device in home.devices:
|
||||
if isinstance(device, AsyncFullFlushShutter):
|
||||
devices.append(HomematicipCoverShutter(home, device))
|
||||
|
||||
if devices:
|
||||
async_add_entities(devices)
|
||||
|
||||
|
||||
class HomematicipCoverShutter(HomematicipGenericDevice, CoverDevice):
|
||||
"""Representation of a HomematicIP Cloud cover device."""
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return current position of cover."""
|
||||
return int(self._device.shutterLevel * 100)
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
position = kwargs[ATTR_POSITION]
|
||||
level = position / 100.0
|
||||
await self._device.set_shutter_level(level)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
if self._device.shutterLevel is not None:
|
||||
return self._device.shutterLevel == 0
|
||||
return None
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Open the cover."""
|
||||
await self._device.set_shutter_level(1)
|
||||
|
||||
async def async_close_cover(self, **kwargs):
|
||||
"""Close the cover."""
|
||||
await self._device.set_shutter_level(0)
|
||||
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop the device if in motion."""
|
||||
await self._device.set_shutter_stop()
|
||||
@@ -46,7 +46,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['scsgate']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}),
|
||||
vol.Required(CONF_DEVICES):
|
||||
cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
DEPENDENCIES = ['velbus']
|
||||
|
||||
@@ -4,8 +4,7 @@ Support for Wink Covers.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.wink/
|
||||
"""
|
||||
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \
|
||||
ATTR_POSITION
|
||||
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
|
||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['wink']
|
||||
@@ -54,7 +53,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
|
||||
"""Return the current position of cover shutter."""
|
||||
if self.wink.state() is not None:
|
||||
return int(self.wink.state()*100)
|
||||
return STATE_UNKNOWN
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
|
||||
19
homeassistant/components/daikin/.translations/de.json
Normal file
19
homeassistant/components/daikin/.translations/de.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ger\u00e4t ist bereits konfiguriert",
|
||||
"device_fail": "Unerwarteter Fehler beim Erstellen des Ger\u00e4ts.",
|
||||
"device_timeout": "Zeit\u00fcberschreitung beim Verbinden mit dem Ger\u00e4t."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Geben Sie die IP-Adresse Ihrer Daikin AC ein.",
|
||||
"title": "Daikin AC konfigurieren"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
||||
11
homeassistant/components/daikin/.translations/hu.json
Normal file
11
homeassistant/components/daikin/.translations/hu.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Kiszolg\u00e1l\u00f3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
homeassistant/components/daikin/.translations/nl.json
Normal file
19
homeassistant/components/daikin/.translations/nl.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Apparaat is al geconfigureerd",
|
||||
"device_fail": "Onverwachte fout bij het aanmaken van een apparaat.",
|
||||
"device_timeout": "Time-out voor verbinding met het apparaat."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Voer het IP-adres van uw Daikin AC in.",
|
||||
"title": "Daikin AC instellen"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
||||
19
homeassistant/components/daikin/.translations/no.json
Normal file
19
homeassistant/components/daikin/.translations/no.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Enheten er allerede konfigurert",
|
||||
"device_fail": "Uventet feil under oppretting av enheten.",
|
||||
"device_timeout": "Tidsavbrudd for tilkobling til enheten."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Vert"
|
||||
},
|
||||
"description": "Angi IP-adressen til din Daikin AC.",
|
||||
"title": "Konfigurer Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
||||
19
homeassistant/components/daikin/.translations/pl.json
Normal file
19
homeassistant/components/daikin/.translations/pl.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane",
|
||||
"device_fail": "Nieoczekiwany b\u0142\u0105d tworzenia urz\u0105dzenia.",
|
||||
"device_timeout": "Limit czasu pod\u0142\u0105czenia do urz\u0105dzenia."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Wprowad\u017a adres IP Daikin AC.",
|
||||
"title": "Konfiguracja Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
||||
16
homeassistant/components/daikin/.translations/pt-BR.json
Normal file
16
homeassistant/components/daikin/.translations/pt-BR.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "O dispositivo j\u00e1 est\u00e1 configurado",
|
||||
"device_fail": "Erro inesperado ao criar dispositivo.",
|
||||
"device_timeout": "Excedido tempo limite conectando ao dispositivo"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Digite o endere\u00e7o IP do seu AC Daikin.",
|
||||
"title": "Configurar o AC Daikin"
|
||||
}
|
||||
},
|
||||
"title": "AC Daikin"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user