mirror of
https://github.com/home-assistant/core.git
synced 2026-01-04 14:55:39 +01:00
Compare commits
724 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebaf7f8c00 | ||
|
|
355005114b | ||
|
|
8f529b20d7 | ||
|
|
b2faa67ab7 | ||
|
|
95371fe4a6 | ||
|
|
2d980f2a92 | ||
|
|
b6d3a199ce | ||
|
|
731753b604 | ||
|
|
cf24687024 | ||
|
|
ef93d48d50 | ||
|
|
9982867d66 | ||
|
|
bdfd473aaa | ||
|
|
7e3d0f0700 | ||
|
|
c03b137130 | ||
|
|
85dbf1eed3 | ||
|
|
237ac08076 | ||
|
|
2e973c7572 | ||
|
|
e980d1b9fe | ||
|
|
26b7c2de7e | ||
|
|
ab826b8fe2 | ||
|
|
a9a8cbbd10 | ||
|
|
3655fefec2 | ||
|
|
ff33cbd22f | ||
|
|
e343f5521c | ||
|
|
f7bc44955c | ||
|
|
df2c3cdded | ||
|
|
f504e5ef61 | ||
|
|
8bf58e1df5 | ||
|
|
90183bd682 | ||
|
|
88ec73ed8f | ||
|
|
7baffed7b7 | ||
|
|
cc4d29d42a | ||
|
|
10c1378195 | ||
|
|
cf3a97ff3c | ||
|
|
222ba96b3e | ||
|
|
6239a523cc | ||
|
|
7eb6e49df7 | ||
|
|
7e91c0dc83 | ||
|
|
b1a05e7605 | ||
|
|
cd90bb4161 | ||
|
|
17d0fe02c7 | ||
|
|
43b140be5e | ||
|
|
9ded16ccc3 | ||
|
|
91dc0c3731 | ||
|
|
6e4a99cec0 | ||
|
|
20bd14defb | ||
|
|
20fa6c5383 | ||
|
|
e2a1e21c8d | ||
|
|
45878c6df0 | ||
|
|
3e5233d115 | ||
|
|
b50c93ccb7 | ||
|
|
50a66abd80 | ||
|
|
7106d9e9d4 | ||
|
|
daf9d28565 | ||
|
|
33860bf23c | ||
|
|
fa196e5889 | ||
|
|
9500bb1ac8 | ||
|
|
b3548f1ead | ||
|
|
f74e976be1 | ||
|
|
dc55718bc3 | ||
|
|
6551c53e32 | ||
|
|
a27d49d022 | ||
|
|
95c43d634b | ||
|
|
a5b9f5040f | ||
|
|
e83a9aace4 | ||
|
|
1e4463957d | ||
|
|
326787ef1a | ||
|
|
15f4ed74ac | ||
|
|
b7b4224429 | ||
|
|
5088e7ee49 | ||
|
|
11004bcf34 | ||
|
|
83db673bd0 | ||
|
|
6b3e4ca7bd | ||
|
|
aa176312a5 | ||
|
|
6235aae196 | ||
|
|
764ea06795 | ||
|
|
9c52a3ce22 | ||
|
|
c6d9ceca63 | ||
|
|
fee87cd6ed | ||
|
|
71ab8a9b1a | ||
|
|
a795093705 | ||
|
|
0c0c471447 | ||
|
|
dc7e5e3af4 | ||
|
|
a9389d2d43 | ||
|
|
993a02b8c4 | ||
|
|
29c2b2fe79 | ||
|
|
73197c9a6c | ||
|
|
1a5048baaf | ||
|
|
9718a17351 | ||
|
|
6feacbbfe1 | ||
|
|
4ea71b0602 | ||
|
|
2ceb4d2d1e | ||
|
|
708334c0c2 | ||
|
|
b5272f2bc7 | ||
|
|
80867cc9b7 | ||
|
|
f92b392a24 | ||
|
|
220054a6c3 | ||
|
|
0904ff45fe | ||
|
|
d6752d2270 | ||
|
|
373e3b12d8 | ||
|
|
e985f30247 | ||
|
|
22bf4d0783 | ||
|
|
1cbb5b8e51 | ||
|
|
ac79ff9e24 | ||
|
|
0bf10b0b09 | ||
|
|
31981fde7e | ||
|
|
4fce051838 | ||
|
|
bd450ee9ff | ||
|
|
879924fea4 | ||
|
|
3b0db291dd | ||
|
|
a71cc67efb | ||
|
|
c5905ee5ca | ||
|
|
3edcc9420a | ||
|
|
b022dde622 | ||
|
|
1187e0aea5 | ||
|
|
a401be9b1b | ||
|
|
49a9b61f6f | ||
|
|
ed683d8c2c | ||
|
|
d4061b73b0 | ||
|
|
9305ea9a6b | ||
|
|
7b0c88c54b | ||
|
|
253e154a79 | ||
|
|
daf48a3b1f | ||
|
|
5ac0469ef9 | ||
|
|
fccaf7f919 | ||
|
|
f198859441 | ||
|
|
a302112879 | ||
|
|
6a8eb8d0a1 | ||
|
|
f23708ce6f | ||
|
|
0dd3640c78 | ||
|
|
d0da265166 | ||
|
|
3ca3fe7015 | ||
|
|
ef8253c549 | ||
|
|
78e29cd3fa | ||
|
|
9c178cf488 | ||
|
|
e6d002c377 | ||
|
|
70281a148b | ||
|
|
d4b092706a | ||
|
|
db536797be | ||
|
|
d9d27733d1 | ||
|
|
5f16f3c3a6 | ||
|
|
d3672f36fb | ||
|
|
3cf6c76f8b | ||
|
|
434d1d7d63 | ||
|
|
401e22fc0c | ||
|
|
8eb4e77365 | ||
|
|
1f863830e1 | ||
|
|
cd29b47924 | ||
|
|
edb3722abd | ||
|
|
bc036cc2fe | ||
|
|
8778b707f1 | ||
|
|
05ae8f9dd4 | ||
|
|
d199f57aa8 | ||
|
|
f47e080f37 | ||
|
|
cfc5ebbfb0 | ||
|
|
6971e84ddf | ||
|
|
397c4336bc | ||
|
|
e00ed84d84 | ||
|
|
7b28963a88 | ||
|
|
5a00cc5afc | ||
|
|
cb3d62eeef | ||
|
|
241d87e9d3 | ||
|
|
b2789d9883 | ||
|
|
7bb60068d7 | ||
|
|
1c23a36f46 | ||
|
|
0ea5a73e8d | ||
|
|
6df3c480b3 | ||
|
|
61f7a39748 | ||
|
|
5961f2f577 | ||
|
|
61bf4d8a29 | ||
|
|
44477f3d32 | ||
|
|
ed45dff6e8 | ||
|
|
2a35a3901e | ||
|
|
ebff253cc9 | ||
|
|
f5d3aa1826 | ||
|
|
cffb704311 | ||
|
|
58af332d21 | ||
|
|
ef2c8b2e5b | ||
|
|
9fa7906aef | ||
|
|
c6c5d40056 | ||
|
|
d7cd1a2b4b | ||
|
|
7e8973a315 | ||
|
|
3f87d41381 | ||
|
|
8d9da4e7b9 | ||
|
|
93e3596e5a | ||
|
|
c434ad6af5 | ||
|
|
99c6622ee2 | ||
|
|
9d4dbd7d97 | ||
|
|
fa3f6ca2c7 | ||
|
|
1d78393680 | ||
|
|
951d7154b8 | ||
|
|
3f28b30860 | ||
|
|
83dec7173c | ||
|
|
d16e6c8524 | ||
|
|
052c094425 | ||
|
|
40e0966d7f | ||
|
|
ad4d5666fe | ||
|
|
7f896bfb40 | ||
|
|
83dd961fde | ||
|
|
a1dac28e4b | ||
|
|
e5c3a4be80 | ||
|
|
707b7c202d | ||
|
|
78c38749ab | ||
|
|
670c75e844 | ||
|
|
419725e1a9 | ||
|
|
cfc175d71d | ||
|
|
8310f4a1cf | ||
|
|
5022cf8a6c | ||
|
|
2cd99e5a97 | ||
|
|
26f2e3dd8b | ||
|
|
fc67f5eef3 | ||
|
|
2aeb0efc7c | ||
|
|
a99ba0a1d4 | ||
|
|
e903f7ffda | ||
|
|
cf249e3e5e | ||
|
|
a8a21ee28d | ||
|
|
1a76603f53 | ||
|
|
089e15e046 | ||
|
|
b59d69f313 | ||
|
|
eb7db1f763 | ||
|
|
429f09deb3 | ||
|
|
5167658a1d | ||
|
|
6bf3f9e748 | ||
|
|
9d56730b8d | ||
|
|
26cf5acd5b | ||
|
|
9190fe1c21 | ||
|
|
0c34c50d2f | ||
|
|
757ba3b60e | ||
|
|
882c4b73ae | ||
|
|
4455a287fc | ||
|
|
5db7d702c8 | ||
|
|
540d22d603 | ||
|
|
56a43436d7 | ||
|
|
1393766659 | ||
|
|
68d72931c4 | ||
|
|
0c0184973b | ||
|
|
59ec469722 | ||
|
|
68472b8241 | ||
|
|
9380fca97e | ||
|
|
c3b1121d77 | ||
|
|
4d3d51635d | ||
|
|
a372053eac | ||
|
|
2e120061b4 | ||
|
|
3a6eac216c | ||
|
|
6671cbb96d | ||
|
|
9e386938bb | ||
|
|
6be52208fc | ||
|
|
dd55ff87c8 | ||
|
|
b637b48bd8 | ||
|
|
42fb886d71 | ||
|
|
9290f245bf | ||
|
|
c41ef65da6 | ||
|
|
744dd42ad3 | ||
|
|
27f0331f0f | ||
|
|
394ffc40b6 | ||
|
|
d5f5273c31 | ||
|
|
af2402ea59 | ||
|
|
71a0274b12 | ||
|
|
3f498bd042 | ||
|
|
ee5e1fa355 | ||
|
|
f2d8f3bcb8 | ||
|
|
ff4204244b | ||
|
|
a54e242245 | ||
|
|
849665b9ca | ||
|
|
315f83e1ea | ||
|
|
4b7f85518f | ||
|
|
59d78b060f | ||
|
|
bd4ff6fc21 | ||
|
|
5fcea074c4 | ||
|
|
7369af0639 | ||
|
|
b872cb95c7 | ||
|
|
3338ebd4c1 | ||
|
|
7b176aa7c9 | ||
|
|
c722091967 | ||
|
|
e922dd10ba | ||
|
|
4cffef7f2b | ||
|
|
31b1b0c044 | ||
|
|
2de1193fd9 | ||
|
|
c12bbddc0b | ||
|
|
086c71525e | ||
|
|
06a64c0167 | ||
|
|
c1ed9edd26 | ||
|
|
1d7d82fde5 | ||
|
|
592e1dc96a | ||
|
|
6e81ae096e | ||
|
|
c8266c6692 | ||
|
|
8fda705377 | ||
|
|
5d6562a73f | ||
|
|
9285831fa1 | ||
|
|
760047f964 | ||
|
|
8683eeb908 | ||
|
|
75e236de73 | ||
|
|
169abe637c | ||
|
|
07d90c6c55 | ||
|
|
a66db59359 | ||
|
|
bed1b96f5a | ||
|
|
13106a9b55 | ||
|
|
7598067b55 | ||
|
|
7a22fbe961 | ||
|
|
738089c57c | ||
|
|
5e7d4a57a3 | ||
|
|
1061c369f1 | ||
|
|
88c0a92857 | ||
|
|
0c770520ed | ||
|
|
92e28067d8 | ||
|
|
d1636dab31 | ||
|
|
13aa935147 | ||
|
|
29c3f1618f | ||
|
|
37a47b5a59 | ||
|
|
2e62afabdc | ||
|
|
a8f5e8699a | ||
|
|
4218efddcd | ||
|
|
aec320dc19 | ||
|
|
acf684de05 | ||
|
|
ce1e8f8ace | ||
|
|
b55c7a5ba2 | ||
|
|
52ff232797 | ||
|
|
769dda735d | ||
|
|
02bf07d9df | ||
|
|
b92b24e8a4 | ||
|
|
0cfbb9ce91 | ||
|
|
c9976718d4 | ||
|
|
b61a250321 | ||
|
|
ae0dd33e2e | ||
|
|
0d93bf9a45 | ||
|
|
178bf736f6 | ||
|
|
a559c06d6b | ||
|
|
cc1891ef2b | ||
|
|
05d8c57212 | ||
|
|
3abdf217bb | ||
|
|
6a0c9a718e | ||
|
|
abd329d707 | ||
|
|
04fdde0e86 | ||
|
|
2b3019bdf4 | ||
|
|
cf0147098a | ||
|
|
aeb21596a0 | ||
|
|
cf5f02b347 | ||
|
|
467a59a6ed | ||
|
|
da622abb79 | ||
|
|
16cbc2d07f | ||
|
|
952a1b3513 | ||
|
|
a5402739b7 | ||
|
|
704c9d8582 | ||
|
|
2e5eb4d9dc | ||
|
|
5d693277f0 | ||
|
|
3cb20c7b4d | ||
|
|
4210835dcd | ||
|
|
15a160a630 | ||
|
|
c78850a983 | ||
|
|
8e3a70e568 | ||
|
|
06340c9875 | ||
|
|
31dd327c59 | ||
|
|
38e2926a48 | ||
|
|
19722a0ef8 | ||
|
|
a6f8c3f662 | ||
|
|
11d5671ee0 | ||
|
|
20601cfc2d | ||
|
|
07972c84a1 | ||
|
|
7f0a50ce31 | ||
|
|
03845ef490 | ||
|
|
37706c2731 | ||
|
|
71e3047f5c | ||
|
|
0954eefa9f | ||
|
|
90f71261c5 | ||
|
|
13af61e103 | ||
|
|
b0b3620b2b | ||
|
|
e07b6dfe0b | ||
|
|
3f5018459f | ||
|
|
2feab82396 | ||
|
|
1decba0052 | ||
|
|
1667481342 | ||
|
|
8e276295eb | ||
|
|
284d4d49c7 | ||
|
|
c3eff5773b | ||
|
|
4d471622f6 | ||
|
|
90f2990b9e | ||
|
|
b197b8bab3 | ||
|
|
a148f3e2a9 | ||
|
|
d732f8eca2 | ||
|
|
b0c1c37cd5 | ||
|
|
4c36ffd0ef | ||
|
|
fbc1c41673 | ||
|
|
2e6346ca43 | ||
|
|
122e21a3c4 | ||
|
|
33a54043e9 | ||
|
|
d09ee8ac82 | ||
|
|
1ca2e5226b | ||
|
|
b5e3d8c337 | ||
|
|
f0fbdd6a26 | ||
|
|
e5c0e4336d | ||
|
|
c69a790ede | ||
|
|
5bc2e78ab4 | ||
|
|
68bda1c732 | ||
|
|
3137099348 | ||
|
|
22a80cf733 | ||
|
|
9edf1e5151 | ||
|
|
6159f8b0ce | ||
|
|
d4cde2bfbf | ||
|
|
760f822dce | ||
|
|
b24f9f5dfa | ||
|
|
134eeecd65 | ||
|
|
3b5e5cbcd6 | ||
|
|
9e4c8f45d6 | ||
|
|
121dba659c | ||
|
|
9aaf11de8c | ||
|
|
ea7b1e4573 | ||
|
|
8444b9ba03 | ||
|
|
38e371c5d9 | ||
|
|
750c96709e | ||
|
|
940d5fb2ee | ||
|
|
0a2b266742 | ||
|
|
f511920a04 | ||
|
|
1b7bfec247 | ||
|
|
f5632a5da5 | ||
|
|
1e28c752e1 | ||
|
|
677714ecab | ||
|
|
e2aadc3227 | ||
|
|
c45b240026 | ||
|
|
32d652884b | ||
|
|
3528f8e647 | ||
|
|
3e6cf8f59f | ||
|
|
06d959ed43 | ||
|
|
7f47d601f1 | ||
|
|
8b1bdda0fa | ||
|
|
70ce9bb7bc | ||
|
|
45fdda3f5d | ||
|
|
caaf4f5694 | ||
|
|
3ddad83a84 | ||
|
|
35b6064581 | ||
|
|
c600d28b6a | ||
|
|
909c3bdd63 | ||
|
|
4b8c38819e | ||
|
|
7d1960baba | ||
|
|
af89e7c50f | ||
|
|
c93879074b | ||
|
|
f476d781ec | ||
|
|
720b05c301 | ||
|
|
c9b6567265 | ||
|
|
9abdbf3db6 | ||
|
|
f879ac0993 | ||
|
|
70b901017f | ||
|
|
7bfe0e1c00 | ||
|
|
fb39641eef | ||
|
|
b7e03f6973 | ||
|
|
7597e30efb | ||
|
|
a7248d4574 | ||
|
|
51dbc988f9 | ||
|
|
71333a15f7 | ||
|
|
bac71d3d22 | ||
|
|
d77e88645e | ||
|
|
2d104f95d7 | ||
|
|
81e21b90c9 | ||
|
|
d1ad2cc225 | ||
|
|
8d65230a36 | ||
|
|
2cc6263092 | ||
|
|
ad79dc673d | ||
|
|
da3342f1aa | ||
|
|
29db43edb2 | ||
|
|
24e9c62fe7 | ||
|
|
81d4338b93 | ||
|
|
96e5acda1a | ||
|
|
726cf9b1c4 | ||
|
|
631ecf578e | ||
|
|
273a7af330 | ||
|
|
4b674b1d16 | ||
|
|
dd45e99302 | ||
|
|
d0ddc28f96 | ||
|
|
9ab8f78b19 | ||
|
|
732009c668 | ||
|
|
d20e0f5c95 | ||
|
|
98a4b1e9ac | ||
|
|
917df1af00 | ||
|
|
f13f723a04 | ||
|
|
7b68f344e3 | ||
|
|
824e59499f | ||
|
|
ff9377d1d9 | ||
|
|
cf0d0fb33e | ||
|
|
75c372021d | ||
|
|
3aaf619fc3 | ||
|
|
e375b63902 | ||
|
|
e5861241c7 | ||
|
|
e205092693 | ||
|
|
fa98a27df7 | ||
|
|
c899875abb | ||
|
|
bab079f649 | ||
|
|
92a5068977 | ||
|
|
3cba2e695c | ||
|
|
bfa1c55803 | ||
|
|
ba2b8512c5 | ||
|
|
e2a56721d3 | ||
|
|
672fc61bb2 | ||
|
|
6490ec87f3 | ||
|
|
dc102a96a9 | ||
|
|
b91a061cef | ||
|
|
7eaf8640d0 | ||
|
|
8e311686d0 | ||
|
|
5a22e7d211 | ||
|
|
7de0e1e39a | ||
|
|
4501bdb4a0 | ||
|
|
4a265f37e0 | ||
|
|
c3f58b8c74 | ||
|
|
422ccc1a28 | ||
|
|
eb59f2dd3c | ||
|
|
399040de46 | ||
|
|
0dbfd77402 | ||
|
|
61a2d09342 | ||
|
|
345c886dec | ||
|
|
b9043ef7a7 | ||
|
|
356040d506 | ||
|
|
0431e38aa2 | ||
|
|
4c32ad3b48 | ||
|
|
bc8d323bdd | ||
|
|
a1c914dfeb | ||
|
|
093285f92f | ||
|
|
9ea5afd109 | ||
|
|
01925fdfff | ||
|
|
7840b1e387 | ||
|
|
e4898bb05c | ||
|
|
f4974f58fe | ||
|
|
2b2502c91c | ||
|
|
4a4c07ac1b | ||
|
|
03bce84c32 | ||
|
|
4f9fc9b39f | ||
|
|
069b819679 | ||
|
|
90197b6ec9 | ||
|
|
42790d3e97 | ||
|
|
e78f4d1b65 | ||
|
|
354c8f3409 | ||
|
|
579b77ba4c | ||
|
|
b52e8525ac | ||
|
|
dc75db6376 | ||
|
|
7d68ec1110 | ||
|
|
c352b6fa59 | ||
|
|
0bd94d8b56 | ||
|
|
3e2a9afff0 | ||
|
|
d4b239d1d4 | ||
|
|
b2a9e203f2 | ||
|
|
dc1534c6d1 | ||
|
|
589554ad16 | ||
|
|
33d6c99f19 | ||
|
|
1f74adae2a | ||
|
|
7a77951bb4 | ||
|
|
ad47ece5c6 | ||
|
|
5ee4718e24 | ||
|
|
a5cb4e6c2b | ||
|
|
4fd2f773ad | ||
|
|
564ad7e22a | ||
|
|
329d9dfc06 | ||
|
|
2258c56d34 | ||
|
|
e90abf1007 | ||
|
|
eaee55175b | ||
|
|
539b86e079 | ||
|
|
127395ae8d | ||
|
|
eca1f050cd | ||
|
|
0d681b0ba6 | ||
|
|
94d38a1c6b | ||
|
|
cfd1aec741 | ||
|
|
eec6722cf4 | ||
|
|
fce275d29c | ||
|
|
5eda5f2f7b | ||
|
|
5ab580ab34 | ||
|
|
5613816476 | ||
|
|
e9c7fe184d | ||
|
|
41bb4760f7 | ||
|
|
ee3f17d5c7 | ||
|
|
18d37ff0fd | ||
|
|
7fe0d8b2f4 | ||
|
|
8b42d0c471 | ||
|
|
213171769d | ||
|
|
d5813cf167 | ||
|
|
3e59ffb33a | ||
|
|
a0a54dfd5b | ||
|
|
3bfe9e757e | ||
|
|
9fdf123a2f | ||
|
|
aeaf694552 | ||
|
|
3d1c8ee467 | ||
|
|
98b92c78c0 | ||
|
|
2ac16b12c1 | ||
|
|
df67093441 | ||
|
|
c475a876ce | ||
|
|
90c18d1c15 | ||
|
|
78b6439ee6 | ||
|
|
092c146eae | ||
|
|
44a98fb77a | ||
|
|
03d93bea34 | ||
|
|
9dbac9b033 | ||
|
|
9e86f0498b | ||
|
|
03de658d4d | ||
|
|
3ea8c25e1f | ||
|
|
6e01ea5929 | ||
|
|
c6ccbed828 | ||
|
|
e58836f99f | ||
|
|
874225dd67 | ||
|
|
fc6cc22b6d | ||
|
|
39ea9a8c90 | ||
|
|
93af3c57ff | ||
|
|
d1acb0326c | ||
|
|
35005474f8 | ||
|
|
3a45481b5b | ||
|
|
aa7635398a | ||
|
|
d3658c4af9 | ||
|
|
dfe38b4d5a | ||
|
|
fcb84d951e | ||
|
|
27eede724c | ||
|
|
258beb9cd3 | ||
|
|
da882672bd | ||
|
|
60dfd68083 | ||
|
|
6e4a6cc069 | ||
|
|
a1c524d372 | ||
|
|
3160fa5de8 | ||
|
|
d4b7057a3d | ||
|
|
227a1b919b | ||
|
|
0121e3cb04 | ||
|
|
da108f1999 | ||
|
|
d376049a3f | ||
|
|
7f462ba0ec | ||
|
|
b7ef4dddb4 | ||
|
|
b72499a949 | ||
|
|
b770acd9a7 | ||
|
|
56b0d2e99f | ||
|
|
8e7f783da8 | ||
|
|
1913d07c39 | ||
|
|
2a85ed7236 | ||
|
|
cba3a5b055 | ||
|
|
d2246d5a4f | ||
|
|
72419a1afe | ||
|
|
a7325ebe1f | ||
|
|
27d50d388f | ||
|
|
25712f16b3 | ||
|
|
5cb0c92e78 | ||
|
|
a7a16e4317 | ||
|
|
c19665fed4 | ||
|
|
6588dceadd | ||
|
|
4b30cbbf3b | ||
|
|
41ac2a3c73 | ||
|
|
b8257866f5 | ||
|
|
849a93e0a6 | ||
|
|
1c251009fe | ||
|
|
201fd4afee | ||
|
|
3e0c6c176a | ||
|
|
44fdfdf695 | ||
|
|
fea1c921fc | ||
|
|
5e3e441aa0 | ||
|
|
a1e6e04a5e | ||
|
|
2002497d09 | ||
|
|
baeb791d84 | ||
|
|
9c9df793b4 | ||
|
|
05922ac56a | ||
|
|
34deaf8849 | ||
|
|
cc38981a38 | ||
|
|
19514ea500 | ||
|
|
00918af94d | ||
|
|
e054e4da1b | ||
|
|
0c945d81c8 | ||
|
|
1ca09ea36f | ||
|
|
8ce2d701c2 | ||
|
|
9c1a539f90 | ||
|
|
ca7911ec47 | ||
|
|
0d0bda9658 | ||
|
|
7705666061 | ||
|
|
e82e75baf3 | ||
|
|
481f6e09fa | ||
|
|
72e746d240 | ||
|
|
05c0717167 | ||
|
|
67b5b5bc85 | ||
|
|
d076251b18 | ||
|
|
e59ba28fe6 | ||
|
|
f63dba5521 | ||
|
|
f43d9ba680 | ||
|
|
aec134c47a | ||
|
|
901dd9acca | ||
|
|
7a52bbdf24 | ||
|
|
f2203e52ef | ||
|
|
1586d3000c | ||
|
|
d0aeb90c22 | ||
|
|
cb542a90eb | ||
|
|
3824582e68 | ||
|
|
2682d26f79 | ||
|
|
308b7fb385 | ||
|
|
4efe86327d | ||
|
|
ff78a5b04b | ||
|
|
453cbb7c60 | ||
|
|
77026a2242 | ||
|
|
501f2b0a93 | ||
|
|
117ea9e553 | ||
|
|
beed82ab12 | ||
|
|
601f2df5a7 | ||
|
|
34d369ba26 | ||
|
|
e2465da7c2 | ||
|
|
6bd120ff1d | ||
|
|
188f5de5ae | ||
|
|
06af76404f | ||
|
|
629c4a0bf5 | ||
|
|
3f06b4eb9b | ||
|
|
0db13a99aa | ||
|
|
4e3faf6108 | ||
|
|
9583947012 | ||
|
|
50fb59477a | ||
|
|
20f6cb7cc7 | ||
|
|
6b08e6e769 | ||
|
|
ee696643cd | ||
|
|
a059cc860a | ||
|
|
cfe5db4350 | ||
|
|
dcd7b9a529 | ||
|
|
f858938ada | ||
|
|
e96635b5c1 | ||
|
|
d2d715faa8 | ||
|
|
99f7b7f42d | ||
|
|
6433f2e2e3 | ||
|
|
f4a54e2483 | ||
|
|
85c40d29ee | ||
|
|
a4a175440b | ||
|
|
8bec4a55d1 | ||
|
|
50f63ed4c5 | ||
|
|
c9e34e236d | ||
|
|
38318ed15f | ||
|
|
553d3c0179 | ||
|
|
20879726b0 | ||
|
|
3db766e2ec | ||
|
|
1eac6408f5 | ||
|
|
e73f31d829 | ||
|
|
839b58c600 | ||
|
|
71d00062e5 | ||
|
|
80f98b9ea2 |
58
.coveragerc
58
.coveragerc
@@ -28,6 +28,9 @@ omit =
|
||||
homeassistant/components/apple_tv.py
|
||||
homeassistant/components/*/apple_tv.py
|
||||
|
||||
homeassistant/components/aqualogic.py
|
||||
homeassistant/components/*/aqualogic.py
|
||||
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
@@ -42,6 +45,7 @@ omit =
|
||||
|
||||
homeassistant/components/asterisk_mbox.py
|
||||
homeassistant/components/*/asterisk_mbox.py
|
||||
homeassistant/components/*/asterisk_cdr.py
|
||||
|
||||
homeassistant/components/august.py
|
||||
homeassistant/components/*/august.py
|
||||
@@ -52,7 +56,7 @@ omit =
|
||||
homeassistant/components/bbb_gpio.py
|
||||
homeassistant/components/*/bbb_gpio.py
|
||||
|
||||
homeassistant/components/blink.py
|
||||
homeassistant/components/blink/*
|
||||
homeassistant/components/*/blink.py
|
||||
|
||||
homeassistant/components/bloomsky.py
|
||||
@@ -92,17 +96,26 @@ omit =
|
||||
homeassistant/components/ecobee.py
|
||||
homeassistant/components/*/ecobee.py
|
||||
|
||||
homeassistant/components/edp_redy.py
|
||||
homeassistant/components/*/edp_redy.py
|
||||
|
||||
homeassistant/components/egardia.py
|
||||
homeassistant/components/*/egardia.py
|
||||
|
||||
homeassistant/components/elkm1/*
|
||||
homeassistant/components/*/elkm1.py
|
||||
|
||||
homeassistant/components/enocean.py
|
||||
homeassistant/components/*/enocean.py
|
||||
|
||||
homeassistant/components/envisalink.py
|
||||
homeassistant/components/*/envisalink.py
|
||||
|
||||
homeassistant/components/evohome.py
|
||||
homeassistant/components/*/evohome.py
|
||||
|
||||
homeassistant/components/fritzbox.py
|
||||
homeassistant/components/switch/fritzbox.py
|
||||
homeassistant/components/*/fritzbox.py
|
||||
|
||||
homeassistant/components/ecovacs.py
|
||||
homeassistant/components/*/ecovacs.py
|
||||
@@ -123,6 +136,7 @@ omit =
|
||||
homeassistant/components/hangouts/const.py
|
||||
homeassistant/components/hangouts/hangouts_bot.py
|
||||
homeassistant/components/hangouts/hangups_utils.py
|
||||
homeassistant/components/hangouts/intents.py
|
||||
homeassistant/components/*/hangouts.py
|
||||
|
||||
homeassistant/components/hdmi_cec.py
|
||||
@@ -140,6 +154,9 @@ omit =
|
||||
homeassistant/components/homematicip_cloud.py
|
||||
homeassistant/components/*/homematicip_cloud.py
|
||||
|
||||
homeassistant/components/huawei_lte.py
|
||||
homeassistant/components/*/huawei_lte.py
|
||||
|
||||
homeassistant/components/hydrawise.py
|
||||
homeassistant/components/*/hydrawise.py
|
||||
|
||||
@@ -183,6 +200,9 @@ omit =
|
||||
homeassistant/components/linode.py
|
||||
homeassistant/components/*/linode.py
|
||||
|
||||
homeassistant/components/logi_circle.py
|
||||
homeassistant/components/*/logi_circle.py
|
||||
|
||||
homeassistant/components/lutron.py
|
||||
homeassistant/components/*/lutron.py
|
||||
|
||||
@@ -228,6 +248,9 @@ omit =
|
||||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/opentherm_gw.py
|
||||
homeassistant/components/*/opentherm_gw.py
|
||||
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/*/openuv.py
|
||||
|
||||
@@ -267,6 +290,9 @@ omit =
|
||||
homeassistant/components/scsgate.py
|
||||
homeassistant/components/*/scsgate.py
|
||||
|
||||
homeassistant/components/simplisafe/__init__.py
|
||||
homeassistant/components/*/simplisafe.py
|
||||
|
||||
homeassistant/components/sisyphus.py
|
||||
homeassistant/components/*/sisyphus.py
|
||||
|
||||
@@ -299,6 +325,9 @@ omit =
|
||||
|
||||
homeassistant/components/*/thinkingcleaner.py
|
||||
|
||||
homeassistant/components/tibber/*
|
||||
homeassistant/components/*/tibber.py
|
||||
|
||||
homeassistant/components/toon.py
|
||||
homeassistant/components/*/toon.py
|
||||
|
||||
@@ -359,7 +388,7 @@ omit =
|
||||
homeassistant/components/zigbee.py
|
||||
homeassistant/components/*/zigbee.py
|
||||
|
||||
homeassistant/components/zoneminder.py
|
||||
homeassistant/components/zoneminder/*
|
||||
homeassistant/components/*/zoneminder.py
|
||||
|
||||
homeassistant/components/tuya.py
|
||||
@@ -375,7 +404,6 @@ omit =
|
||||
homeassistant/components/alarm_control_panel/ifttt.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py
|
||||
homeassistant/components/alarm_control_panel/totalconnect.py
|
||||
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
|
||||
homeassistant/components/apiai.py
|
||||
@@ -406,7 +434,6 @@ omit =
|
||||
homeassistant/components/camera/xeoma.py
|
||||
homeassistant/components/camera/xiaomi.py
|
||||
homeassistant/components/camera/yi.py
|
||||
homeassistant/components/climate/econet.py
|
||||
homeassistant/components/climate/ephember.py
|
||||
homeassistant/components/climate/eq3btsmart.py
|
||||
homeassistant/components/climate/flexit.py
|
||||
@@ -414,8 +441,8 @@ omit =
|
||||
homeassistant/components/climate/homematic.py
|
||||
homeassistant/components/climate/honeywell.py
|
||||
homeassistant/components/climate/knx.py
|
||||
homeassistant/components/climate/mill.py
|
||||
homeassistant/components/climate/oem.py
|
||||
homeassistant/components/climate/opentherm_gw.py
|
||||
homeassistant/components/climate/proliphix.py
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/climate/sensibo.py
|
||||
@@ -431,7 +458,6 @@ omit =
|
||||
homeassistant/components/cover/myq.py
|
||||
homeassistant/components/cover/opengarage.py
|
||||
homeassistant/components/cover/rpi_gpio.py
|
||||
homeassistant/components/cover/ryobi_gdo.py
|
||||
homeassistant/components/cover/scsgate.py
|
||||
homeassistant/components/device_tracker/actiontec.py
|
||||
homeassistant/components/device_tracker/aruba.py
|
||||
@@ -458,6 +484,7 @@ omit =
|
||||
homeassistant/components/device_tracker/netgear.py
|
||||
homeassistant/components/device_tracker/nmap_tracker.py
|
||||
homeassistant/components/device_tracker/ping.py
|
||||
homeassistant/components/device_tracker/quantum_gateway.py
|
||||
homeassistant/components/device_tracker/ritassist.py
|
||||
homeassistant/components/device_tracker/sky_hub.py
|
||||
homeassistant/components/device_tracker/snmp.py
|
||||
@@ -499,6 +526,7 @@ omit =
|
||||
homeassistant/components/light/lw12wifi.py
|
||||
homeassistant/components/light/mystrom.py
|
||||
homeassistant/components/light/nanoleaf_aurora.py
|
||||
homeassistant/components/light/opple.py
|
||||
homeassistant/components/light/osramlightify.py
|
||||
homeassistant/components/light/piglow.py
|
||||
homeassistant/components/light/rpi_gpio_pwm.py
|
||||
@@ -540,6 +568,7 @@ omit =
|
||||
homeassistant/components/media_player/itunes.py
|
||||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/lg_netcast.py
|
||||
homeassistant/components/media_player/lg_soundbar.py
|
||||
homeassistant/components/media_player/liveboxplaytv.py
|
||||
homeassistant/components/media_player/mediaroom.py
|
||||
homeassistant/components/media_player/mpchc.py
|
||||
@@ -583,6 +612,7 @@ omit =
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/group.py
|
||||
homeassistant/components/notify/hipchat.py
|
||||
homeassistant/components/notify/homematic.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/kodi.py
|
||||
homeassistant/components/notify/lannouncer.py
|
||||
@@ -609,13 +639,13 @@ omit =
|
||||
homeassistant/components/notify/telstra.py
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/notify/yessssms.py
|
||||
homeassistant/components/nuimo_controller.py
|
||||
homeassistant/components/prometheus.py
|
||||
homeassistant/components/rainbird.py
|
||||
homeassistant/components/remember_the_milk/__init__.py
|
||||
homeassistant/components/remote/harmony.py
|
||||
homeassistant/components/remote/itach.py
|
||||
homeassistant/components/route53.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/scene/lifx_cloud.py
|
||||
homeassistant/components/sensor/airvisual.py
|
||||
@@ -668,6 +698,7 @@ omit =
|
||||
homeassistant/components/sensor/fritzbox_netmonitor.py
|
||||
homeassistant/components/sensor/gearbest.py
|
||||
homeassistant/components/sensor/geizhals.py
|
||||
homeassistant/components/sensor/gitlab_ci.py
|
||||
homeassistant/components/sensor/gitter.py
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/google_travel_time.py
|
||||
@@ -676,6 +707,7 @@ omit =
|
||||
homeassistant/components/sensor/haveibeenpwned.py
|
||||
homeassistant/components/sensor/hp_ilo.py
|
||||
homeassistant/components/sensor/htu21d.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/imap_email_content.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/influxdb.py
|
||||
@@ -684,6 +716,7 @@ omit =
|
||||
homeassistant/components/sensor/kwb.py
|
||||
homeassistant/components/sensor/lacrosse.py
|
||||
homeassistant/components/sensor/lastfm.py
|
||||
homeassistant/components/sensor/linky.py
|
||||
homeassistant/components/sensor/linux_battery.py
|
||||
homeassistant/components/sensor/loopenergy.py
|
||||
homeassistant/components/sensor/luftdaten.py
|
||||
@@ -697,6 +730,7 @@ omit =
|
||||
homeassistant/components/sensor/mqtt_room.py
|
||||
homeassistant/components/sensor/mvglive.py
|
||||
homeassistant/components/sensor/nederlandse_spoorwegen.py
|
||||
homeassistant/components/sensor/netatmo_public.py
|
||||
homeassistant/components/sensor/netdata.py
|
||||
homeassistant/components/sensor/netdata_public.py
|
||||
homeassistant/components/sensor/neurio_energy.py
|
||||
@@ -723,6 +757,7 @@ omit =
|
||||
homeassistant/components/sensor/radarr.py
|
||||
homeassistant/components/sensor/rainbird.py
|
||||
homeassistant/components/sensor/ripple.py
|
||||
homeassistant/components/sensor/rtorrent.py
|
||||
homeassistant/components/sensor/scrape.py
|
||||
homeassistant/components/sensor/sense.py
|
||||
homeassistant/components/sensor/sensehat.py
|
||||
@@ -740,6 +775,7 @@ omit =
|
||||
homeassistant/components/sensor/sonarr.py
|
||||
homeassistant/components/sensor/speedtest.py
|
||||
homeassistant/components/sensor/spotcrime.py
|
||||
homeassistant/components/sensor/starlingbank.py
|
||||
homeassistant/components/sensor/steam_online.py
|
||||
homeassistant/components/sensor/supervisord.py
|
||||
homeassistant/components/sensor/swiss_hydrological_data.py
|
||||
@@ -751,7 +787,7 @@ omit =
|
||||
homeassistant/components/sensor/tank_utility.py
|
||||
homeassistant/components/sensor/ted5000.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
homeassistant/components/sensor/tibber.py
|
||||
homeassistant/components/sensor/thermoworks_smoke.py
|
||||
homeassistant/components/sensor/time_date.py
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/trafikverket_weatherstation.py
|
||||
@@ -759,7 +795,6 @@ omit =
|
||||
homeassistant/components/sensor/travisci.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/ups.py
|
||||
homeassistant/components/sensor/uscis.py
|
||||
homeassistant/components/sensor/vasttrafik.py
|
||||
@@ -793,6 +828,7 @@ omit =
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rainbird.py
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/recswitch.py
|
||||
homeassistant/components/switch/rpi_rf.py
|
||||
homeassistant/components/switch/snmp.py
|
||||
homeassistant/components/switch/switchbot.py
|
||||
@@ -809,10 +845,12 @@ omit =
|
||||
homeassistant/components/tts/picotts.py
|
||||
homeassistant/components/vacuum/mqtt.py
|
||||
homeassistant/components/vacuum/roomba.py
|
||||
homeassistant/components/water_heater/econet.py
|
||||
homeassistant/components/watson_iot.py
|
||||
homeassistant/components/weather/bom.py
|
||||
homeassistant/components/weather/buienradar.py
|
||||
homeassistant/components/weather/darksky.py
|
||||
homeassistant/components/weather/met.py
|
||||
homeassistant/components/weather/metoffice.py
|
||||
homeassistant/components/weather/openweathermap.py
|
||||
homeassistant/components/weather/zamg.py
|
||||
|
||||
149
CODEOWNERS
149
CODEOWNERS
@@ -2,57 +2,68 @@
|
||||
# when the code that they own is touched.
|
||||
# https://github.com/blog/2392-introducing-code-owners
|
||||
|
||||
# Home Assistant Core
|
||||
setup.py @home-assistant/core
|
||||
homeassistant/*.py @home-assistant/core
|
||||
homeassistant/helpers/* @home-assistant/core
|
||||
homeassistant/util/* @home-assistant/core
|
||||
homeassistant/components/api.py @home-assistant/core
|
||||
homeassistant/components/auth/* @home-assistant/core
|
||||
homeassistant/components/automation/* @home-assistant/core
|
||||
homeassistant/components/cloud/* @home-assistant/core
|
||||
homeassistant/components/config/* @home-assistant/core
|
||||
homeassistant/components/configurator.py @home-assistant/core
|
||||
homeassistant/components/group.py @home-assistant/core
|
||||
homeassistant/components/conversation/* @home-assistant/core
|
||||
homeassistant/components/frontend/* @home-assistant/core
|
||||
homeassistant/components/group/* @home-assistant/core
|
||||
homeassistant/components/history.py @home-assistant/core
|
||||
homeassistant/components/http/* @home-assistant/core
|
||||
homeassistant/components/input_*.py @home-assistant/core
|
||||
homeassistant/components/introduction.py @home-assistant/core
|
||||
homeassistant/components/logger.py @home-assistant/core
|
||||
homeassistant/components/lovelace/* @home-assistant/core
|
||||
homeassistant/components/mqtt/* @home-assistant/core
|
||||
homeassistant/components/panel_custom.py @home-assistant/core
|
||||
homeassistant/components/panel_iframe.py @home-assistant/core
|
||||
homeassistant/components/persistent_notification.py @home-assistant/core
|
||||
homeassistant/components/onboarding/* @home-assistant/core
|
||||
homeassistant/components/persistent_notification/* @home-assistant/core
|
||||
homeassistant/components/scene/__init__.py @home-assistant/core
|
||||
homeassistant/components/scene/hass.py @home-assistant/core
|
||||
homeassistant/components/script.py @home-assistant/core
|
||||
homeassistant/components/shell_command.py @home-assistant/core
|
||||
homeassistant/components/sun.py @home-assistant/core
|
||||
homeassistant/components/updater.py @home-assistant/core
|
||||
homeassistant/components/weblink.py @home-assistant/core
|
||||
homeassistant/components/weblink/* @home-assistant/core
|
||||
homeassistant/components/websocket_api.py @home-assistant/core
|
||||
homeassistant/components/zone.py @home-assistant/core
|
||||
homeassistant/components/zone/* @home-assistant/core
|
||||
|
||||
# HomeAssistant developer Teams
|
||||
# Home Assistant Developer Teams
|
||||
Dockerfile @home-assistant/docker
|
||||
virtualization/Docker/* @home-assistant/docker
|
||||
|
||||
homeassistant/components/zwave/* @home-assistant/z-wave
|
||||
homeassistant/components/*/zwave.py @home-assistant/z-wave
|
||||
|
||||
homeassistant/components/hassio.py @home-assistant/hassio
|
||||
homeassistant/components/hassio/* @home-assistant/hassio
|
||||
|
||||
# Individual components
|
||||
# Individual platforms
|
||||
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
|
||||
homeassistant/components/binary_sensor/hikvision.py @mezz64
|
||||
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/binary_sensor/threshold.py @fabaff
|
||||
homeassistant/components/camera/yi.py @bachya
|
||||
homeassistant/components/climate/ephember.py @ttroy50
|
||||
homeassistant/components/climate/eq3btsmart.py @rytilahti
|
||||
homeassistant/components/climate/mill.py @danielhiversen
|
||||
homeassistant/components/climate/sensibo.py @andrey-git
|
||||
homeassistant/components/cover/group.py @cdce8p
|
||||
homeassistant/components/cover/template.py @PhracturedBlue
|
||||
homeassistant/components/device_tracker/automatic.py @armills
|
||||
homeassistant/components/device_tracker/huawei_router.py @abmantis
|
||||
homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan
|
||||
homeassistant/components/device_tracker/tile.py @bachya
|
||||
homeassistant/components/history_graph.py @andrey-git
|
||||
homeassistant/components/light/lifx.py @amelchio
|
||||
homeassistant/components/influx.py @fabaff
|
||||
homeassistant/components/light/lifx_legacy.py @amelchio
|
||||
homeassistant/components/light/tplink.py @rytilahti
|
||||
homeassistant/components/light/yeelight.py @rytilahti
|
||||
@@ -63,63 +74,173 @@ homeassistant/components/media_player/kodi.py @armills
|
||||
homeassistant/components/media_player/liveboxplaytv.py @pschmitt
|
||||
homeassistant/components/media_player/mediaroom.py @dgomes
|
||||
homeassistant/components/media_player/monoprice.py @etsinko
|
||||
homeassistant/components/media_player/mpd.py @fabaff
|
||||
homeassistant/components/media_player/sonos.py @amelchio
|
||||
homeassistant/components/media_player/xiaomi_tv.py @fattdev
|
||||
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
|
||||
homeassistant/components/no_ip.py @fabaff
|
||||
homeassistant/components/notify/file.py @fabaff
|
||||
homeassistant/components/notify/flock.py @fabaff
|
||||
homeassistant/components/notify/instapush.py @fabaff
|
||||
homeassistant/components/notify/mastodon.py @fabaff
|
||||
homeassistant/components/notify/smtp.py @fabaff
|
||||
homeassistant/components/notify/syslog.py @fabaff
|
||||
homeassistant/components/notify/xmpp.py @fabaff
|
||||
homeassistant/components/plant.py @ChristianKuehnel
|
||||
homeassistant/components/scene/lifx_cloud.py @amelchio
|
||||
homeassistant/components/sensor/airvisual.py @bachya
|
||||
homeassistant/components/sensor/alpha_vantage.py @fabaff
|
||||
homeassistant/components/sensor/bitcoin.py @fabaff
|
||||
homeassistant/components/sensor/cpuspeed.py @fabaff
|
||||
homeassistant/components/sensor/cups.py @fabaff
|
||||
homeassistant/components/sensor/darksky.py @fabaff
|
||||
homeassistant/components/sensor/file.py @fabaff
|
||||
homeassistant/components/sensor/filter.py @dgomes
|
||||
homeassistant/components/sensor/fixer.py @fabaff
|
||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||
homeassistant/components/sensor/gitter.py @fabaff
|
||||
homeassistant/components/sensor/glances.py @fabaff
|
||||
homeassistant/components/sensor/gpsd.py @fabaff
|
||||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||
homeassistant/components/sensor/jewish_calendar.py @tsvi
|
||||
homeassistant/components/sensor/linux_battery.py @fabaff
|
||||
homeassistant/components/sensor/luftdaten.py @fabaff
|
||||
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/sensor/min_max.py @fabaff
|
||||
homeassistant/components/sensor/moon.py @fabaff
|
||||
homeassistant/components/sensor/netdata.py @fabaff
|
||||
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
|
||||
homeassistant/components/sensor/pi_hole.py @fabaff
|
||||
homeassistant/components/sensor/pollen.py @bachya
|
||||
homeassistant/components/sensor/pvoutput.py @fabaff
|
||||
homeassistant/components/sensor/qnap.py @colinodell
|
||||
homeassistant/components/sensor/scrape.py @fabaff
|
||||
homeassistant/components/sensor/serial.py @fabaff
|
||||
homeassistant/components/sensor/shodan.py @fabaff
|
||||
homeassistant/components/sensor/sma.py @kellerza
|
||||
homeassistant/components/sensor/sql.py @dgomes
|
||||
homeassistant/components/sensor/statistics.py @fabaff
|
||||
homeassistant/components/sensor/swiss*.py @fabaff
|
||||
homeassistant/components/sensor/sytadin.py @gautric
|
||||
homeassistant/components/sensor/tibber.py @danielhiversen
|
||||
homeassistant/components/sensor/upnp.py @dgomes
|
||||
homeassistant/components/sensor/time_data.py @fabaff
|
||||
homeassistant/components/sensor/version.py @fabaff
|
||||
homeassistant/components/sensor/waqi.py @andrey-git
|
||||
homeassistant/components/sensor/worldclock.py @fabaff
|
||||
homeassistant/components/shiftr.py @fabaff
|
||||
homeassistant/components/spaceapi.py @fabaff
|
||||
homeassistant/components/switch/tplink.py @rytilahti
|
||||
homeassistant/components/vacuum/roomba.py @pschmitt
|
||||
homeassistant/components/weather/__init__.py @fabaff
|
||||
homeassistant/components/weather/darksky.py @fabaff
|
||||
homeassistant/components/weather/demo.py @fabaff
|
||||
homeassistant/components/weather/met.py @danielhiversen
|
||||
homeassistant/components/weather/openweathermap.py @fabaff
|
||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||
|
||||
# A
|
||||
homeassistant/components/arduino.py @fabaff
|
||||
homeassistant/components/*/arduino.py @fabaff
|
||||
homeassistant/components/*/arest.py @fabaff
|
||||
homeassistant/components/*/axis.py @kane610
|
||||
|
||||
# B
|
||||
homeassistant/components/blink/* @fronzbot
|
||||
homeassistant/components/*/blink.py @fronzbot
|
||||
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/*/broadlink.py @danielhiversen
|
||||
|
||||
# C
|
||||
homeassistant/components/counter/* @fabaff
|
||||
|
||||
# D
|
||||
homeassistant/components/*/deconz.py @kane610
|
||||
homeassistant/components/digital_ocean.py @fabaff
|
||||
homeassistant/components/*/digital_ocean.py @fabaff
|
||||
homeassistant/components/dweet.py @fabaff
|
||||
homeassistant/components/*/dweet.py @fabaff
|
||||
|
||||
# E
|
||||
homeassistant/components/ecovacs.py @OverloadUT
|
||||
homeassistant/components/*/ecovacs.py @OverloadUT
|
||||
homeassistant/components/*/edp_redy.py @abmantis
|
||||
homeassistant/components/edp_redy.py @abmantis
|
||||
homeassistant/components/eight_sleep.py @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
|
||||
# H
|
||||
homeassistant/components/hive.py @Rendili @KJonline
|
||||
homeassistant/components/*/hive.py @Rendili @KJonline
|
||||
homeassistant/components/homekit/* @cdce8p
|
||||
homeassistant/components/huawei_lte.py @scop
|
||||
homeassistant/components/*/huawei_lte.py @scop
|
||||
|
||||
# K
|
||||
homeassistant/components/knx.py @Julius2342
|
||||
homeassistant/components/*/knx.py @Julius2342
|
||||
homeassistant/components/konnected.py @heythisisnate
|
||||
homeassistant/components/*/konnected.py @heythisisnate
|
||||
|
||||
# L
|
||||
homeassistant/components/lifx.py @amelchio
|
||||
homeassistant/components/*/lifx.py @amelchio
|
||||
|
||||
# M
|
||||
homeassistant/components/matrix.py @tinloaf
|
||||
homeassistant/components/*/matrix.py @tinloaf
|
||||
homeassistant/components/openuv.py @bachya
|
||||
homeassistant/components/melissa.py @kennedyshead
|
||||
homeassistant/components/*/melissa.py @kennedyshead
|
||||
homeassistant/components/*/mystrom.py @fabaff
|
||||
|
||||
# O
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/*/openuv.py @bachya
|
||||
|
||||
# Q
|
||||
homeassistant/components/qwikswitch.py @kellerza
|
||||
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
|
||||
|
||||
# T
|
||||
homeassistant/components/tahoma.py @philklei
|
||||
homeassistant/components/*/tahoma.py @philklei
|
||||
homeassistant/components/tesla.py @zabuldon
|
||||
homeassistant/components/*/tesla.py @zabuldon
|
||||
homeassistant/components/tellduslive.py @molobrakos @fredrike
|
||||
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
|
||||
homeassistant/components/tesla.py @zabuldon
|
||||
homeassistant/components/*/tesla.py @zabuldon
|
||||
homeassistant/components/thethingsnetwork.py @fabaff
|
||||
homeassistant/components/*/thethingsnetwork.py @fabaff
|
||||
homeassistant/components/tibber/* @danielhiversen
|
||||
homeassistant/components/*/tibber.py @danielhiversen
|
||||
homeassistant/components/tradfri/* @ggravlingen
|
||||
homeassistant/components/*/tradfri.py @ggravlingen
|
||||
|
||||
# U
|
||||
homeassistant/components/unifi.py @kane610
|
||||
homeassistant/components/switch/unifi.py @kane610
|
||||
homeassistant/components/upcloud.py @scop
|
||||
homeassistant/components/*/upcloud.py @scop
|
||||
|
||||
# V
|
||||
homeassistant/components/velux.py @Julius2342
|
||||
homeassistant/components/*/velux.py @Julius2342
|
||||
|
||||
# X
|
||||
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
|
||||
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
|
||||
|
||||
# Z
|
||||
homeassistant/components/zoneminder/ @rohankapoorcom
|
||||
homeassistant/components/*/zoneminder.py @rohankapoorcom
|
||||
|
||||
# Other code
|
||||
homeassistant/scripts/check_config.py @kellerza
|
||||
|
||||
@@ -10,5 +10,5 @@ The process is straight-forward.
|
||||
- Ensure tests work.
|
||||
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
|
||||
|
||||
Still interested? Then you should take a peek at the [developer documentation](https://home-assistant.io/developers/) to get more details.
|
||||
Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details.
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ Featured integrations
|
||||
|
||||
|screenshot-components|
|
||||
|
||||
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://home-assistant.io/developers/architecture/>`__ and the `section on creating your own
|
||||
components <https://home-assistant.io/developers/creating_components/>`__.
|
||||
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://developers.home-assistant.io/docs/en/architecture_index.html>`__ and the `section on creating your own
|
||||
components <https://developers.home-assistant.io/docs/en/creating_component_index.html>`__.
|
||||
|
||||
If you run into issues while using Home Assistant or during development
|
||||
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
|
||||
|
||||
@@ -19,4 +19,4 @@ Indices and tables
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _Home Assistant developers: https://home-assistant.io/developers/
|
||||
.. _Home Assistant developers: https://developers.home-assistant.io/
|
||||
|
||||
@@ -7,7 +7,6 @@ import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from typing import List, Dict, Any # noqa pylint: disable=unused-import
|
||||
|
||||
|
||||
@@ -20,15 +19,34 @@ from homeassistant.const import (
|
||||
)
|
||||
|
||||
|
||||
def attempt_use_uvloop() -> None:
|
||||
def set_loop() -> None:
|
||||
"""Attempt to use uvloop."""
|
||||
import asyncio
|
||||
from asyncio.events import BaseDefaultEventLoopPolicy
|
||||
|
||||
try:
|
||||
import uvloop
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
except ImportError:
|
||||
pass
|
||||
policy = None
|
||||
|
||||
if sys.platform == 'win32':
|
||||
if hasattr(asyncio, 'WindowsProactorEventLoopPolicy'):
|
||||
# pylint: disable=no-member
|
||||
policy = asyncio.WindowsProactorEventLoopPolicy()
|
||||
else:
|
||||
class ProactorPolicy(BaseDefaultEventLoopPolicy):
|
||||
"""Event loop policy to create proactor loops."""
|
||||
|
||||
_loop_factory = asyncio.ProactorEventLoop
|
||||
|
||||
policy = ProactorPolicy()
|
||||
else:
|
||||
try:
|
||||
import uvloop
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
policy = uvloop.EventLoopPolicy()
|
||||
|
||||
if policy is not None:
|
||||
asyncio.set_event_loop_policy(policy)
|
||||
|
||||
|
||||
def validate_python() -> None:
|
||||
@@ -240,51 +258,39 @@ def cmdline() -> List[str]:
|
||||
return [arg for arg in sys.argv if arg != '--daemon']
|
||||
|
||||
|
||||
def setup_and_run_hass(config_dir: str,
|
||||
args: argparse.Namespace) -> int:
|
||||
async def setup_and_run_hass(config_dir: str,
|
||||
args: argparse.Namespace) -> int:
|
||||
"""Set up HASS and run."""
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant import bootstrap, core
|
||||
|
||||
# Run a simple daemon runner process on Windows to handle restarts
|
||||
if os.name == 'nt' and '--runner' not in sys.argv:
|
||||
nt_args = cmdline() + ['--runner']
|
||||
while True:
|
||||
try:
|
||||
subprocess.check_call(nt_args)
|
||||
sys.exit(0)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if exc.returncode != RESTART_EXIT_CODE:
|
||||
sys.exit(exc.returncode)
|
||||
hass = core.HomeAssistant()
|
||||
|
||||
if args.demo_mode:
|
||||
config = {
|
||||
'frontend': {},
|
||||
'demo': {}
|
||||
} # type: Dict[str, Any]
|
||||
hass = bootstrap.from_config_dict(
|
||||
config, config_dir=config_dir, verbose=args.verbose,
|
||||
bootstrap.async_from_config_dict(
|
||||
config, hass, config_dir=config_dir, verbose=args.verbose,
|
||||
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days,
|
||||
log_file=args.log_file, log_no_color=args.log_no_color)
|
||||
else:
|
||||
config_file = ensure_config_file(config_dir)
|
||||
print('Config directory:', config_dir)
|
||||
hass = bootstrap.from_config_file(
|
||||
config_file, verbose=args.verbose, skip_pip=args.skip_pip,
|
||||
await bootstrap.async_from_config_file(
|
||||
config_file, hass, verbose=args.verbose, skip_pip=args.skip_pip,
|
||||
log_rotate_days=args.log_rotate_days, log_file=args.log_file,
|
||||
log_no_color=args.log_no_color)
|
||||
|
||||
if hass is None:
|
||||
return -1
|
||||
|
||||
if args.open_ui:
|
||||
# Imported here to avoid importing asyncio before monkey patch
|
||||
from homeassistant.util.async_ import run_callback_threadsafe
|
||||
|
||||
def open_browser(_: Any) -> None:
|
||||
"""Open the web interface in a browser."""
|
||||
if hass.config.api is not None: # type: ignore
|
||||
if hass.config.api is not None:
|
||||
import webbrowser
|
||||
webbrowser.open(hass.config.api.base_url) # type: ignore
|
||||
webbrowser.open(hass.config.api.base_url)
|
||||
|
||||
run_callback_threadsafe(
|
||||
hass.loop,
|
||||
@@ -292,7 +298,7 @@ def setup_and_run_hass(config_dir: str,
|
||||
EVENT_HOMEASSISTANT_START, open_browser
|
||||
)
|
||||
|
||||
return hass.start()
|
||||
return await hass.async_run()
|
||||
|
||||
|
||||
def try_to_restart() -> None:
|
||||
@@ -347,7 +353,20 @@ def main() -> int:
|
||||
monkey_patch.disable_c_asyncio()
|
||||
monkey_patch.patch_weakref_tasks()
|
||||
|
||||
attempt_use_uvloop()
|
||||
set_loop()
|
||||
|
||||
# Run a simple daemon runner process on Windows to handle restarts
|
||||
if os.name == 'nt' and '--runner' not in sys.argv:
|
||||
nt_args = cmdline() + ['--runner']
|
||||
while True:
|
||||
try:
|
||||
subprocess.check_call(nt_args)
|
||||
sys.exit(0)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
except subprocess.CalledProcessError as exc:
|
||||
if exc.returncode != RESTART_EXIT_CODE:
|
||||
sys.exit(exc.returncode)
|
||||
|
||||
args = get_arguments()
|
||||
|
||||
@@ -366,11 +385,12 @@ def main() -> int:
|
||||
if args.pid_file:
|
||||
write_pid(args.pid_file)
|
||||
|
||||
exit_code = setup_and_run_hass(config_dir, args)
|
||||
from homeassistant.util.async_ import asyncio_run
|
||||
exit_code = asyncio_run(setup_and_run_hass(config_dir, args))
|
||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||
try_to_restart()
|
||||
|
||||
return exit_code
|
||||
return exit_code # type: ignore # mypy cannot yet infer it
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -16,6 +16,9 @@ from . import auth_store, models
|
||||
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
|
||||
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
|
||||
|
||||
EVENT_USER_ADDED = 'user_added'
|
||||
EVENT_USER_REMOVED = 'user_removed'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_MfaModuleDict = Dict[str, MultiFactorAuthModule]
|
||||
_ProviderKey = Tuple[str, Optional[str]]
|
||||
@@ -126,23 +129,38 @@ class AuthManager:
|
||||
|
||||
async def async_create_system_user(self, name: str) -> models.User:
|
||||
"""Create a system user."""
|
||||
return await self._store.async_create_user(
|
||||
user = await self._store.async_create_user(
|
||||
name=name,
|
||||
system_generated=True,
|
||||
is_active=True,
|
||||
groups=[],
|
||||
)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_USER_ADDED, {
|
||||
'user_id': user.id
|
||||
})
|
||||
|
||||
return user
|
||||
|
||||
async def async_create_user(self, name: str) -> models.User:
|
||||
"""Create a user."""
|
||||
group = (await self._store.async_get_groups())[0]
|
||||
kwargs = {
|
||||
'name': name,
|
||||
'is_active': True,
|
||||
'groups': [group]
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
if await self._user_should_be_owner():
|
||||
kwargs['is_owner'] = True
|
||||
|
||||
return await self._store.async_create_user(**kwargs)
|
||||
user = await self._store.async_create_user(**kwargs)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_USER_ADDED, {
|
||||
'user_id': user.id
|
||||
})
|
||||
|
||||
return user
|
||||
|
||||
async def async_get_or_create_user(self, credentials: models.Credentials) \
|
||||
-> models.User:
|
||||
@@ -162,12 +180,18 @@ class AuthManager:
|
||||
info = await auth_provider.async_user_meta_for_credentials(
|
||||
credentials)
|
||||
|
||||
return await self._store.async_create_user(
|
||||
user = await self._store.async_create_user(
|
||||
credentials=credentials,
|
||||
name=info.name,
|
||||
is_active=info.is_active,
|
||||
)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_USER_ADDED, {
|
||||
'user_id': user.id
|
||||
})
|
||||
|
||||
return user
|
||||
|
||||
async def async_link_user(self, user: models.User,
|
||||
credentials: models.Credentials) -> None:
|
||||
"""Link credentials to an existing user."""
|
||||
@@ -185,6 +209,10 @@ class AuthManager:
|
||||
|
||||
await self._store.async_remove_user(user)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_USER_REMOVED, {
|
||||
'user_id': user.id
|
||||
})
|
||||
|
||||
async def async_activate_user(self, user: models.User) -> None:
|
||||
"""Activate a user."""
|
||||
await self._store.async_activate_user(user)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
"""Storage for auth models."""
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import hmac
|
||||
from logging import getLogger
|
||||
from typing import Any, Dict, List, Optional # noqa: F401
|
||||
import hmac
|
||||
|
||||
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import models
|
||||
from .permissions import DEFAULT_POLICY
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = 'auth'
|
||||
INITIAL_GROUP_NAME = 'All Access'
|
||||
|
||||
|
||||
class AuthStore:
|
||||
@@ -28,7 +30,17 @@ class AuthStore:
|
||||
"""Initialize the auth store."""
|
||||
self.hass = hass
|
||||
self._users = None # type: Optional[Dict[str, models.User]]
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._groups = None # type: Optional[Dict[str, models.Group]]
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
|
||||
async def async_get_groups(self) -> List[models.Group]:
|
||||
"""Retrieve all users."""
|
||||
if self._groups is None:
|
||||
await self._async_load()
|
||||
assert self._groups is not None
|
||||
|
||||
return list(self._groups.values())
|
||||
|
||||
async def async_get_users(self) -> List[models.User]:
|
||||
"""Retrieve all users."""
|
||||
@@ -50,14 +62,20 @@ class AuthStore:
|
||||
self, name: Optional[str], is_owner: Optional[bool] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
system_generated: Optional[bool] = None,
|
||||
credentials: Optional[models.Credentials] = None) -> models.User:
|
||||
credentials: Optional[models.Credentials] = None,
|
||||
groups: Optional[List[models.Group]] = None) -> models.User:
|
||||
"""Create a new user."""
|
||||
if self._users is None:
|
||||
await self._async_load()
|
||||
assert self._users is not None
|
||||
|
||||
assert self._users is not None
|
||||
assert self._groups is not None
|
||||
|
||||
kwargs = {
|
||||
'name': name
|
||||
'name': name,
|
||||
# Until we get group management, we just put everyone in the
|
||||
# same group.
|
||||
'groups': groups or [],
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
if is_owner is not None:
|
||||
@@ -213,14 +231,45 @@ class AuthStore:
|
||||
if self._users is not None:
|
||||
return
|
||||
|
||||
users = OrderedDict() # type: Dict[str, models.User]
|
||||
|
||||
if data is None:
|
||||
self._users = users
|
||||
self._set_defaults()
|
||||
return
|
||||
|
||||
users = OrderedDict() # type: Dict[str, models.User]
|
||||
groups = OrderedDict() # type: Dict[str, models.Group]
|
||||
|
||||
# When creating objects we mention each attribute explicetely. This
|
||||
# prevents crashing if user rolls back HA version after a new property
|
||||
# was added.
|
||||
|
||||
for group_dict in data.get('groups', []):
|
||||
groups[group_dict['id']] = models.Group(
|
||||
name=group_dict['name'],
|
||||
id=group_dict['id'],
|
||||
policy=group_dict.get('policy', DEFAULT_POLICY),
|
||||
)
|
||||
|
||||
migrate_group = None
|
||||
|
||||
if not groups:
|
||||
migrate_group = models.Group(
|
||||
name=INITIAL_GROUP_NAME,
|
||||
policy=DEFAULT_POLICY
|
||||
)
|
||||
groups[migrate_group.id] = migrate_group
|
||||
|
||||
for user_dict in data['users']:
|
||||
users[user_dict['id']] = models.User(**user_dict)
|
||||
users[user_dict['id']] = models.User(
|
||||
name=user_dict['name'],
|
||||
groups=[groups[group_id] for group_id
|
||||
in user_dict.get('group_ids', [])],
|
||||
id=user_dict['id'],
|
||||
is_owner=user_dict['is_owner'],
|
||||
is_active=user_dict['is_active'],
|
||||
system_generated=user_dict['system_generated'],
|
||||
)
|
||||
if migrate_group is not None and not user_dict['system_generated']:
|
||||
users[user_dict['id']].groups = [migrate_group]
|
||||
|
||||
for cred_dict in data['credentials']:
|
||||
users[cred_dict['user_id']].credentials.append(models.Credentials(
|
||||
@@ -275,6 +324,7 @@ class AuthStore:
|
||||
)
|
||||
users[rt_dict['user_id']].refresh_tokens[token.id] = token
|
||||
|
||||
self._groups = groups
|
||||
self._users = users
|
||||
|
||||
@callback
|
||||
@@ -289,10 +339,12 @@ class AuthStore:
|
||||
def _data_to_save(self) -> Dict:
|
||||
"""Return the data to store."""
|
||||
assert self._users is not None
|
||||
assert self._groups is not None
|
||||
|
||||
users = [
|
||||
{
|
||||
'id': user.id,
|
||||
'group_ids': [group.id for group in user.groups],
|
||||
'is_owner': user.is_owner,
|
||||
'is_active': user.is_active,
|
||||
'name': user.name,
|
||||
@@ -301,6 +353,18 @@ class AuthStore:
|
||||
for user in self._users.values()
|
||||
]
|
||||
|
||||
groups = []
|
||||
for group in self._groups.values():
|
||||
g_dict = {
|
||||
'name': group.name,
|
||||
'id': group.id,
|
||||
} # type: Dict[str, Any]
|
||||
|
||||
if group.policy is not DEFAULT_POLICY:
|
||||
g_dict['policy'] = group.policy
|
||||
|
||||
groups.append(g_dict)
|
||||
|
||||
credentials = [
|
||||
{
|
||||
'id': credential.id,
|
||||
@@ -337,6 +401,22 @@ class AuthStore:
|
||||
|
||||
return {
|
||||
'users': users,
|
||||
'groups': groups,
|
||||
'credentials': credentials,
|
||||
'refresh_tokens': refresh_tokens,
|
||||
}
|
||||
|
||||
def _set_defaults(self) -> None:
|
||||
"""Set default values for auth store."""
|
||||
self._users = OrderedDict() # type: Dict[str, models.User]
|
||||
|
||||
# Add default group
|
||||
all_access_group = models.Group(
|
||||
name=INITIAL_GROUP_NAME,
|
||||
policy=DEFAULT_POLICY,
|
||||
)
|
||||
|
||||
groups = OrderedDict() # type: Dict[str, models.Group]
|
||||
groups[all_access_group.id] = all_access_group
|
||||
|
||||
self._groups = groups
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
from datetime import timedelta
|
||||
|
||||
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
|
||||
MFA_SESSION_EXPIRATION = timedelta(minutes=5)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Plugable auth modules for Home Assistant."""
|
||||
from datetime import timedelta
|
||||
import importlib
|
||||
import logging
|
||||
import types
|
||||
@@ -23,8 +22,6 @@ MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ID): str,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SESSION_EXPIRATION = timedelta(minutes=5)
|
||||
|
||||
DATA_REQS = 'mfa_auth_module_reqs_processed'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -34,6 +31,7 @@ class MultiFactorAuthModule:
|
||||
"""Multi-factor Auth Module of validation function."""
|
||||
|
||||
DEFAULT_TITLE = 'Unnamed auth module'
|
||||
MAX_RETRY_TIME = 3
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
|
||||
"""Initialize an auth module."""
|
||||
@@ -84,7 +82,7 @@ class MultiFactorAuthModule:
|
||||
"""Return whether user is setup."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def async_validation(
|
||||
async def async_validate(
|
||||
self, user_id: str, user_input: Dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -77,7 +77,7 @@ class InsecureExampleModule(MultiFactorAuthModule):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def async_validation(
|
||||
async def async_validate(
|
||||
self, user_id: str, user_input: Dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
for data in self._data:
|
||||
|
||||
325
homeassistant/auth/mfa_modules/notify.py
Normal file
325
homeassistant/auth/mfa_modules/notify.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""HMAC-based One-time Password auth module.
|
||||
|
||||
Sending HOTP through notify service
|
||||
"""
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Optional, Tuple, List # noqa: F401
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \
|
||||
MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow
|
||||
|
||||
REQUIREMENTS = ['pyotp==2.2.6']
|
||||
|
||||
CONF_MESSAGE = 'message'
|
||||
|
||||
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({
|
||||
vol.Optional(CONF_INCLUDE): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_EXCLUDE): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_MESSAGE,
|
||||
default='{} is your Home Assistant login code'): str
|
||||
}, extra=vol.PREVENT_EXTRA)
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = 'auth_module.notify'
|
||||
STORAGE_USERS = 'users'
|
||||
STORAGE_USER_ID = 'user_id'
|
||||
|
||||
INPUT_FIELD_CODE = 'code'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _generate_secret() -> str:
|
||||
"""Generate a secret."""
|
||||
import pyotp
|
||||
return str(pyotp.random_base32())
|
||||
|
||||
|
||||
def _generate_random() -> int:
|
||||
"""Generate a 8 digit number."""
|
||||
import pyotp
|
||||
return int(pyotp.random_base32(length=8, chars=list('1234567890')))
|
||||
|
||||
|
||||
def _generate_otp(secret: str, count: int) -> str:
|
||||
"""Generate one time password."""
|
||||
import pyotp
|
||||
return str(pyotp.HOTP(secret).at(count))
|
||||
|
||||
|
||||
def _verify_otp(secret: str, otp: str, count: int) -> bool:
|
||||
"""Verify one time password."""
|
||||
import pyotp
|
||||
return bool(pyotp.HOTP(secret).verify(otp, count))
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class NotifySetting:
|
||||
"""Store notify setting for one user."""
|
||||
|
||||
secret = attr.ib(type=str, factory=_generate_secret) # not persistent
|
||||
counter = attr.ib(type=int, factory=_generate_random) # not persistent
|
||||
notify_service = attr.ib(type=Optional[str], default=None)
|
||||
target = attr.ib(type=Optional[str], default=None)
|
||||
|
||||
|
||||
_UsersDict = Dict[str, NotifySetting]
|
||||
|
||||
|
||||
@MULTI_FACTOR_AUTH_MODULES.register('notify')
|
||||
class NotifyAuthModule(MultiFactorAuthModule):
|
||||
"""Auth module send hmac-based one time password by notify service."""
|
||||
|
||||
DEFAULT_TITLE = 'Notify One-Time Password'
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._user_settings = None # type: Optional[_UsersDict]
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||
self._include = config.get(CONF_INCLUDE, [])
|
||||
self._exclude = config.get(CONF_EXCLUDE, [])
|
||||
self._message_template = config[CONF_MESSAGE]
|
||||
|
||||
@property
|
||||
def input_schema(self) -> vol.Schema:
|
||||
"""Validate login flow input data."""
|
||||
return vol.Schema({INPUT_FIELD_CODE: str})
|
||||
|
||||
async def _async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
data = await self._user_store.async_load()
|
||||
|
||||
if data is None:
|
||||
data = {STORAGE_USERS: {}}
|
||||
|
||||
self._user_settings = {
|
||||
user_id: NotifySetting(**setting)
|
||||
for user_id, setting in data.get(STORAGE_USERS, {}).items()
|
||||
}
|
||||
|
||||
async def _async_save(self) -> None:
|
||||
"""Save data."""
|
||||
if self._user_settings is None:
|
||||
return
|
||||
|
||||
await self._user_store.async_save({STORAGE_USERS: {
|
||||
user_id: attr.asdict(
|
||||
notify_setting, filter=attr.filters.exclude(
|
||||
attr.fields(NotifySetting).secret,
|
||||
attr.fields(NotifySetting).counter,
|
||||
))
|
||||
for user_id, notify_setting
|
||||
in self._user_settings.items()
|
||||
}})
|
||||
|
||||
@callback
|
||||
def aync_get_available_notify_services(self) -> List[str]:
|
||||
"""Return list of notify services."""
|
||||
unordered_services = set()
|
||||
|
||||
for service in self.hass.services.async_services().get('notify', {}):
|
||||
if service not in self._exclude:
|
||||
unordered_services.add(service)
|
||||
|
||||
if self._include:
|
||||
unordered_services &= set(self._include)
|
||||
|
||||
return sorted(unordered_services)
|
||||
|
||||
async def async_setup_flow(self, user_id: str) -> SetupFlow:
|
||||
"""Return a data entry flow handler for setup module.
|
||||
|
||||
Mfa module should extend SetupFlow
|
||||
"""
|
||||
return NotifySetupFlow(
|
||||
self, self.input_schema, user_id,
|
||||
self.aync_get_available_notify_services())
|
||||
|
||||
async def async_setup_user(self, user_id: str, setup_data: Any) -> Any:
|
||||
"""Set up auth module for user."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
self._user_settings[user_id] = NotifySetting(
|
||||
notify_service=setup_data.get('notify_service'),
|
||||
target=setup_data.get('target'),
|
||||
)
|
||||
|
||||
await self._async_save()
|
||||
|
||||
async def async_depose_user(self, user_id: str) -> None:
|
||||
"""Depose auth module for user."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
if self._user_settings.pop(user_id, None):
|
||||
await self._async_save()
|
||||
|
||||
async def async_is_user_setup(self, user_id: str) -> bool:
|
||||
"""Return whether user is setup."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
return user_id in self._user_settings
|
||||
|
||||
async def async_validate(
|
||||
self, user_id: str, user_input: Dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
notify_setting = self._user_settings.get(user_id, None)
|
||||
if notify_setting is None:
|
||||
return False
|
||||
|
||||
# user_input has been validate in caller
|
||||
return await self.hass.async_add_executor_job(
|
||||
_verify_otp, notify_setting.secret,
|
||||
user_input.get(INPUT_FIELD_CODE, ''),
|
||||
notify_setting.counter)
|
||||
|
||||
async def async_initialize_login_mfa_step(self, user_id: str) -> None:
|
||||
"""Generate code and notify user."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
notify_setting = self._user_settings.get(user_id, None)
|
||||
if notify_setting is None:
|
||||
raise ValueError('Cannot find user_id')
|
||||
|
||||
def generate_secret_and_one_time_password() -> str:
|
||||
"""Generate and send one time password."""
|
||||
assert notify_setting
|
||||
# secret and counter are not persistent
|
||||
notify_setting.secret = _generate_secret()
|
||||
notify_setting.counter = _generate_random()
|
||||
return _generate_otp(
|
||||
notify_setting.secret, notify_setting.counter)
|
||||
|
||||
code = await self.hass.async_add_executor_job(
|
||||
generate_secret_and_one_time_password)
|
||||
|
||||
await self.async_notify_user(user_id, code)
|
||||
|
||||
async def async_notify_user(self, user_id: str, code: str) -> None:
|
||||
"""Send code by user's notify service."""
|
||||
if self._user_settings is None:
|
||||
await self._async_load()
|
||||
assert self._user_settings is not None
|
||||
|
||||
notify_setting = self._user_settings.get(user_id, None)
|
||||
if notify_setting is None:
|
||||
_LOGGER.error('Cannot find user %s', user_id)
|
||||
return
|
||||
|
||||
await self.async_notify( # type: ignore
|
||||
code, notify_setting.notify_service, notify_setting.target)
|
||||
|
||||
async def async_notify(self, code: str, notify_service: str,
|
||||
target: Optional[str] = None) -> None:
|
||||
"""Send code by notify service."""
|
||||
data = {'message': self._message_template.format(code)}
|
||||
if target:
|
||||
data['target'] = [target]
|
||||
|
||||
await self.hass.services.async_call('notify', notify_service, data)
|
||||
|
||||
|
||||
class NotifySetupFlow(SetupFlow):
|
||||
"""Handler for the setup flow."""
|
||||
|
||||
def __init__(self, auth_module: NotifyAuthModule,
|
||||
setup_schema: vol.Schema,
|
||||
user_id: str,
|
||||
available_notify_services: List[str]) -> None:
|
||||
"""Initialize the setup flow."""
|
||||
super().__init__(auth_module, setup_schema, user_id)
|
||||
# to fix typing complaint
|
||||
self._auth_module = auth_module # type: NotifyAuthModule
|
||||
self._available_notify_services = available_notify_services
|
||||
self._secret = None # type: Optional[str]
|
||||
self._count = None # type: Optional[int]
|
||||
self._notify_service = None # type: Optional[str]
|
||||
self._target = None # type: Optional[str]
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: Optional[Dict[str, str]] = None) \
|
||||
-> Dict[str, Any]:
|
||||
"""Let user select available notify services."""
|
||||
errors = {} # type: Dict[str, str]
|
||||
|
||||
hass = self._auth_module.hass
|
||||
if user_input:
|
||||
self._notify_service = user_input['notify_service']
|
||||
self._target = user_input.get('target')
|
||||
self._secret = await hass.async_add_executor_job(_generate_secret)
|
||||
self._count = await hass.async_add_executor_job(_generate_random)
|
||||
|
||||
return await self.async_step_setup()
|
||||
|
||||
if not self._available_notify_services:
|
||||
return self.async_abort(reason='no_available_service')
|
||||
|
||||
schema = OrderedDict() # type: Dict[str, Any]
|
||||
schema['notify_service'] = vol.In(self._available_notify_services)
|
||||
schema['target'] = vol.Optional(str)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='init',
|
||||
data_schema=vol.Schema(schema),
|
||||
errors=errors
|
||||
)
|
||||
|
||||
async def async_step_setup(
|
||||
self, user_input: Optional[Dict[str, str]] = None) \
|
||||
-> Dict[str, Any]:
|
||||
"""Verify user can recevie one-time password."""
|
||||
errors = {} # type: Dict[str, str]
|
||||
|
||||
hass = self._auth_module.hass
|
||||
if user_input:
|
||||
verified = await hass.async_add_executor_job(
|
||||
_verify_otp, self._secret, user_input['code'], self._count)
|
||||
if verified:
|
||||
await self._auth_module.async_setup_user(
|
||||
self._user_id, {
|
||||
'notify_service': self._notify_service,
|
||||
'target': self._target,
|
||||
})
|
||||
return self.async_create_entry(
|
||||
title=self._auth_module.name,
|
||||
data={}
|
||||
)
|
||||
|
||||
errors['base'] = 'invalid_code'
|
||||
|
||||
# generate code every time, no retry logic
|
||||
assert self._secret and self._count
|
||||
code = await hass.async_add_executor_job(
|
||||
_generate_otp, self._secret, self._count)
|
||||
|
||||
assert self._notify_service
|
||||
await self._auth_module.async_notify(
|
||||
code, self._notify_service, self._target)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='setup',
|
||||
data_schema=self._setup_schema,
|
||||
description_placeholders={'notify_service': self._notify_service},
|
||||
errors=errors,
|
||||
)
|
||||
@@ -60,13 +60,14 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
"""Auth module validate time-based one time password."""
|
||||
|
||||
DEFAULT_TITLE = 'Time-based One Time Password'
|
||||
MAX_RETRY_TIME = 5
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None:
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._users = None # type: Optional[Dict[str, str]]
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY)
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||
|
||||
@property
|
||||
def input_schema(self) -> vol.Schema:
|
||||
@@ -130,7 +131,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
|
||||
return user_id in self._users # type: ignore
|
||||
|
||||
async def async_validation(
|
||||
async def async_validate(
|
||||
self, user_id: str, user_input: Dict[str, Any]) -> bool:
|
||||
"""Return True if validation passed."""
|
||||
if self._users is None:
|
||||
@@ -149,10 +150,10 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
if ota_secret is None:
|
||||
# even we cannot find user, we still do verify
|
||||
# to make timing the same as if user was found.
|
||||
pyotp.TOTP(DUMMY_SECRET).verify(code)
|
||||
pyotp.TOTP(DUMMY_SECRET).verify(code, valid_window=1)
|
||||
return False
|
||||
|
||||
return bool(pyotp.TOTP(ota_secret).verify(code))
|
||||
return bool(pyotp.TOTP(ota_secret).verify(code, valid_window=1))
|
||||
|
||||
|
||||
class TotpSetupFlow(SetupFlow):
|
||||
|
||||
@@ -7,6 +7,7 @@ import attr
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import permissions as perm_mdl
|
||||
from .util import generate_secret
|
||||
|
||||
TOKEN_TYPE_NORMAL = 'normal'
|
||||
@@ -14,26 +15,59 @@ TOKEN_TYPE_SYSTEM = 'system'
|
||||
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = 'long_lived_access_token'
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class Group:
|
||||
"""A group."""
|
||||
|
||||
name = attr.ib(type=str) # type: Optional[str]
|
||||
policy = attr.ib(type=perm_mdl.PolicyType)
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class User:
|
||||
"""A user."""
|
||||
|
||||
name = attr.ib(type=str) # type: Optional[str]
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
is_owner = attr.ib(type=bool, default=False)
|
||||
is_active = attr.ib(type=bool, default=False)
|
||||
system_generated = attr.ib(type=bool, default=False)
|
||||
|
||||
groups = attr.ib(type=List, factory=list, cmp=False) # type: List[Group]
|
||||
|
||||
# List of credentials of a user.
|
||||
credentials = attr.ib(
|
||||
type=list, default=attr.Factory(list), cmp=False
|
||||
type=list, factory=list, cmp=False
|
||||
) # type: List[Credentials]
|
||||
|
||||
# Tokens associated with a user.
|
||||
refresh_tokens = attr.ib(
|
||||
type=dict, default=attr.Factory(dict), cmp=False
|
||||
type=dict, factory=dict, cmp=False
|
||||
) # type: Dict[str, RefreshToken]
|
||||
|
||||
_permissions = attr.ib(
|
||||
type=perm_mdl.PolicyPermissions,
|
||||
init=False,
|
||||
cmp=False,
|
||||
default=None,
|
||||
)
|
||||
|
||||
@property
|
||||
def permissions(self) -> perm_mdl.AbstractPermissions:
|
||||
"""Return permissions object for user."""
|
||||
if self.is_owner:
|
||||
return perm_mdl.OwnerPermissions
|
||||
|
||||
if self._permissions is not None:
|
||||
return self._permissions
|
||||
|
||||
self._permissions = perm_mdl.PolicyPermissions(
|
||||
perm_mdl.merge_policies([
|
||||
group.policy for group in self.groups]))
|
||||
|
||||
return self._permissions
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
class RefreshToken:
|
||||
@@ -48,12 +82,10 @@ class RefreshToken:
|
||||
validator=attr.validators.in_((
|
||||
TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM,
|
||||
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)))
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
|
||||
token = attr.ib(type=str,
|
||||
default=attr.Factory(lambda: generate_secret(64)))
|
||||
jwt_key = attr.ib(type=str,
|
||||
default=attr.Factory(lambda: generate_secret(64)))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
|
||||
token = attr.ib(type=str, factory=lambda: generate_secret(64))
|
||||
jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64))
|
||||
|
||||
last_used_at = attr.ib(type=Optional[datetime], default=None)
|
||||
last_used_ip = attr.ib(type=Optional[str], default=None)
|
||||
@@ -69,7 +101,7 @@ class Credentials:
|
||||
# Allow the auth provider to store data to represent their auth.
|
||||
data = attr.ib(type=dict)
|
||||
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
is_new = attr.ib(type=bool, default=True)
|
||||
|
||||
|
||||
|
||||
252
homeassistant/auth/permissions.py
Normal file
252
homeassistant/auth/permissions.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""Permissions for Home Assistant."""
|
||||
from typing import ( # noqa: F401
|
||||
cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union)
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import State
|
||||
|
||||
CategoryType = Union[Mapping[str, 'CategoryType'], bool, None]
|
||||
PolicyType = Mapping[str, CategoryType]
|
||||
|
||||
|
||||
# Default policy if group has no policy applied.
|
||||
DEFAULT_POLICY = {
|
||||
"entities": True
|
||||
} # type: PolicyType
|
||||
|
||||
CAT_ENTITIES = 'entities'
|
||||
ENTITY_DOMAINS = 'domains'
|
||||
ENTITY_ENTITY_IDS = 'entity_ids'
|
||||
|
||||
VALUES_SCHEMA = vol.Any(True, vol.Schema({
|
||||
str: True
|
||||
}))
|
||||
|
||||
ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({
|
||||
vol.Optional(ENTITY_DOMAINS): VALUES_SCHEMA,
|
||||
vol.Optional(ENTITY_ENTITY_IDS): VALUES_SCHEMA,
|
||||
}))
|
||||
|
||||
POLICY_SCHEMA = vol.Schema({
|
||||
vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA
|
||||
})
|
||||
|
||||
|
||||
class AbstractPermissions:
|
||||
"""Default permissions class."""
|
||||
|
||||
def check_entity(self, entity_id: str, *keys: str) -> bool:
|
||||
"""Test if we can access entity."""
|
||||
raise NotImplementedError
|
||||
|
||||
def filter_states(self, states: List[State]) -> List[State]:
|
||||
"""Filter a list of states for what the user is allowed to see."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class PolicyPermissions(AbstractPermissions):
|
||||
"""Handle permissions."""
|
||||
|
||||
def __init__(self, policy: PolicyType) -> None:
|
||||
"""Initialize the permission class."""
|
||||
self._policy = policy
|
||||
self._compiled = {} # type: Dict[str, Callable[..., bool]]
|
||||
|
||||
def check_entity(self, entity_id: str, *keys: str) -> bool:
|
||||
"""Test if we can access entity."""
|
||||
func = self._policy_func(CAT_ENTITIES, _compile_entities)
|
||||
return func(entity_id, keys)
|
||||
|
||||
def filter_states(self, states: List[State]) -> List[State]:
|
||||
"""Filter a list of states for what the user is allowed to see."""
|
||||
func = self._policy_func(CAT_ENTITIES, _compile_entities)
|
||||
keys = ('read',)
|
||||
return [entity for entity in states if func(entity.entity_id, keys)]
|
||||
|
||||
def _policy_func(self, category: str,
|
||||
compile_func: Callable[[CategoryType], Callable]) \
|
||||
-> Callable[..., bool]:
|
||||
"""Get a policy function."""
|
||||
func = self._compiled.get(category)
|
||||
|
||||
if func:
|
||||
return func
|
||||
|
||||
func = self._compiled[category] = compile_func(
|
||||
self._policy.get(category))
|
||||
return func
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""Equals check."""
|
||||
# pylint: disable=protected-access
|
||||
return (isinstance(other, PolicyPermissions) and
|
||||
other._policy == self._policy)
|
||||
|
||||
|
||||
class _OwnerPermissions(AbstractPermissions):
|
||||
"""Owner permissions."""
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
def check_entity(self, entity_id: str, *keys: str) -> bool:
|
||||
"""Test if we can access entity."""
|
||||
return True
|
||||
|
||||
def filter_states(self, states: List[State]) -> List[State]:
|
||||
"""Filter a list of states for what the user is allowed to see."""
|
||||
return states
|
||||
|
||||
|
||||
OwnerPermissions = _OwnerPermissions() # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def _compile_entities(policy: CategoryType) \
|
||||
-> Callable[[str, Tuple[str]], bool]:
|
||||
"""Compile policy into a function that tests policy."""
|
||||
# None, Empty Dict, False
|
||||
if not policy:
|
||||
def apply_policy_deny_all(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Decline all."""
|
||||
return False
|
||||
|
||||
return apply_policy_deny_all
|
||||
|
||||
if policy is True:
|
||||
def apply_policy_allow_all(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Approve all."""
|
||||
return True
|
||||
|
||||
return apply_policy_allow_all
|
||||
|
||||
assert isinstance(policy, dict)
|
||||
|
||||
domains = policy.get(ENTITY_DOMAINS)
|
||||
entity_ids = policy.get(ENTITY_ENTITY_IDS)
|
||||
|
||||
funcs = [] # type: List[Callable[[str, Tuple[str]], Union[None, bool]]]
|
||||
|
||||
# The order of these functions matter. The more precise are at the top.
|
||||
# If a function returns None, they cannot handle it.
|
||||
# If a function returns a boolean, that's the result to return.
|
||||
|
||||
# Setting entity_ids to a boolean is final decision for permissions
|
||||
# So return right away.
|
||||
if isinstance(entity_ids, bool):
|
||||
def apply_entity_id_policy(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Test if allowed entity_id."""
|
||||
return entity_ids # type: ignore
|
||||
|
||||
return apply_entity_id_policy
|
||||
|
||||
if entity_ids is not None:
|
||||
def allowed_entity_id(entity_id: str, keys: Tuple[str]) \
|
||||
-> Union[None, bool]:
|
||||
"""Test if allowed entity_id."""
|
||||
return entity_ids.get(entity_id) # type: ignore
|
||||
|
||||
funcs.append(allowed_entity_id)
|
||||
|
||||
if isinstance(domains, bool):
|
||||
def allowed_domain(entity_id: str, keys: Tuple[str]) \
|
||||
-> Union[None, bool]:
|
||||
"""Test if allowed domain."""
|
||||
return domains
|
||||
|
||||
funcs.append(allowed_domain)
|
||||
|
||||
elif domains is not None:
|
||||
def allowed_domain(entity_id: str, keys: Tuple[str]) \
|
||||
-> Union[None, bool]:
|
||||
"""Test if allowed domain."""
|
||||
domain = entity_id.split(".", 1)[0]
|
||||
return domains.get(domain) # type: ignore
|
||||
|
||||
funcs.append(allowed_domain)
|
||||
|
||||
# Can happen if no valid subcategories specified
|
||||
if not funcs:
|
||||
def apply_policy_deny_all_2(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Decline all."""
|
||||
return False
|
||||
|
||||
return apply_policy_deny_all_2
|
||||
|
||||
if len(funcs) == 1:
|
||||
func = funcs[0]
|
||||
|
||||
def apply_policy_func(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Apply a single policy function."""
|
||||
return func(entity_id, keys) is True
|
||||
|
||||
return apply_policy_func
|
||||
|
||||
def apply_policy_funcs(entity_id: str, keys: Tuple[str]) -> bool:
|
||||
"""Apply several policy functions."""
|
||||
for func in funcs:
|
||||
result = func(entity_id, keys)
|
||||
if result is not None:
|
||||
return result
|
||||
return False
|
||||
|
||||
return apply_policy_funcs
|
||||
|
||||
|
||||
def merge_policies(policies: List[PolicyType]) -> PolicyType:
|
||||
"""Merge policies."""
|
||||
new_policy = {} # type: Dict[str, CategoryType]
|
||||
seen = set() # type: Set[str]
|
||||
for policy in policies:
|
||||
for category in policy:
|
||||
if category in seen:
|
||||
continue
|
||||
seen.add(category)
|
||||
new_policy[category] = _merge_policies([
|
||||
policy.get(category) for policy in policies])
|
||||
cast(PolicyType, new_policy)
|
||||
return new_policy
|
||||
|
||||
|
||||
def _merge_policies(sources: List[CategoryType]) -> CategoryType:
|
||||
"""Merge a policy."""
|
||||
# When merging policies, the most permissive wins.
|
||||
# This means we order it like this:
|
||||
# True > Dict > None
|
||||
#
|
||||
# True: allow everything
|
||||
# Dict: specify more granular permissions
|
||||
# None: no opinion
|
||||
#
|
||||
# If there are multiple sources with a dict as policy, we recursively
|
||||
# merge each key in the source.
|
||||
|
||||
policy = None # type: CategoryType
|
||||
seen = set() # type: Set[str]
|
||||
for source in sources:
|
||||
if source is None:
|
||||
continue
|
||||
|
||||
# A source that's True will always win. Shortcut return.
|
||||
if source is True:
|
||||
return True
|
||||
|
||||
assert isinstance(source, dict)
|
||||
|
||||
if policy is None:
|
||||
policy = {}
|
||||
|
||||
assert isinstance(policy, dict)
|
||||
|
||||
for key in source:
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
|
||||
key_sources = []
|
||||
for src in sources:
|
||||
if isinstance(src, dict):
|
||||
key_sources.append(src.get(key))
|
||||
|
||||
policy[key] = _merge_policies(key_sources)
|
||||
|
||||
return policy
|
||||
@@ -15,8 +15,8 @@ from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
from ..auth_store import AuthStore
|
||||
from ..const import MFA_SESSION_EXPIRATION
|
||||
from ..models import Credentials, User, UserMeta # noqa: F401
|
||||
from ..mfa_modules import SESSION_EXPIRATION
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_REQS = 'auth_prov_reqs_processed'
|
||||
@@ -171,6 +171,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
self._auth_manager = auth_provider.hass.auth # type: ignore
|
||||
self.available_mfa_modules = {} # type: Dict[str, str]
|
||||
self.created_at = dt_util.utcnow()
|
||||
self.invalid_mfa_times = 0
|
||||
self.user = None # type: Optional[User]
|
||||
|
||||
async def async_step_init(
|
||||
@@ -212,6 +213,8 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
self, user_input: Optional[Dict[str, str]] = None) \
|
||||
-> Dict[str, Any]:
|
||||
"""Handle the step of mfa validation."""
|
||||
assert self.user
|
||||
|
||||
errors = {}
|
||||
|
||||
auth_module = self._auth_manager.get_auth_mfa_module(
|
||||
@@ -221,25 +224,34 @@ class LoginFlow(data_entry_flow.FlowHandler):
|
||||
# will show invalid_auth_module error
|
||||
return await self.async_step_select_mfa_module(user_input={})
|
||||
|
||||
if user_input is None and hasattr(auth_module,
|
||||
'async_initialize_login_mfa_step'):
|
||||
await auth_module.async_initialize_login_mfa_step(self.user.id)
|
||||
|
||||
if user_input is not None:
|
||||
expires = self.created_at + SESSION_EXPIRATION
|
||||
expires = self.created_at + MFA_SESSION_EXPIRATION
|
||||
if dt_util.utcnow() > expires:
|
||||
return self.async_abort(
|
||||
reason='login_expired'
|
||||
)
|
||||
|
||||
result = await auth_module.async_validation(
|
||||
self.user.id, user_input) # type: ignore
|
||||
result = await auth_module.async_validate(
|
||||
self.user.id, user_input)
|
||||
if not result:
|
||||
errors['base'] = 'invalid_code'
|
||||
self.invalid_mfa_times += 1
|
||||
if self.invalid_mfa_times >= auth_module.MAX_RETRY_TIME > 0:
|
||||
return self.async_abort(
|
||||
reason='too_many_retry'
|
||||
)
|
||||
|
||||
if not errors:
|
||||
return await self.async_finish(self.user)
|
||||
|
||||
description_placeholders = {
|
||||
'mfa_module_name': auth_module.name,
|
||||
'mfa_module_id': auth_module.id
|
||||
} # type: Dict[str, str]
|
||||
'mfa_module_id': auth_module.id,
|
||||
} # type: Dict[str, Optional[str]]
|
||||
|
||||
return self.async_show_form(
|
||||
step_id='mfa',
|
||||
|
||||
@@ -52,7 +52,8 @@ class Data:
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the user data store."""
|
||||
self.hass = hass
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
self._data = None # type: Optional[Dict[str, Any]]
|
||||
|
||||
async def async_load(self) -> None:
|
||||
|
||||
@@ -5,7 +5,6 @@ import os
|
||||
import sys
|
||||
from time import time
|
||||
from collections import OrderedDict
|
||||
|
||||
from typing import Any, Optional, Dict
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -19,7 +18,6 @@ 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.signal import async_register_signal_handling
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -160,7 +158,6 @@ async def async_from_config_dict(config: Dict[str, Any],
|
||||
stop = time()
|
||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
|
||||
|
||||
async_register_signal_handling(hass)
|
||||
return hass
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import itertools as it
|
||||
import logging
|
||||
from typing import Awaitable
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.config as conf_util
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
@@ -21,11 +23,16 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
||||
RESTART_EXIT_CODE)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
|
||||
SERVICE_CHECK_CONFIG = 'check_config'
|
||||
SERVICE_UPDATE_ENTITY = 'update_entity'
|
||||
SCHEMA_UPDATE_ENTITY = vol.Schema({
|
||||
ATTR_ENTITY_ID: cv.entity_id
|
||||
})
|
||||
|
||||
|
||||
def is_on(hass, entity_id=None):
|
||||
@@ -59,61 +66,9 @@ def is_on(hass, entity_id=None):
|
||||
return False
|
||||
|
||||
|
||||
def turn_on(hass, entity_id=None, **service_data):
|
||||
"""Turn specified entity on if possible."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data)
|
||||
|
||||
|
||||
def turn_off(hass, entity_id=None, **service_data):
|
||||
"""Turn specified entity off."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
|
||||
|
||||
|
||||
def toggle(hass, entity_id=None, **service_data):
|
||||
"""Toggle specified entity."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
|
||||
|
||||
|
||||
def stop(hass):
|
||||
"""Stop Home Assistant."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
|
||||
|
||||
|
||||
def restart(hass):
|
||||
"""Stop Home Assistant."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
|
||||
|
||||
|
||||
def check_config(hass):
|
||||
"""Check the config files."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
|
||||
|
||||
|
||||
def reload_core_config(hass):
|
||||
"""Reload the core config."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_reload_core_config(hass):
|
||||
"""Reload the core config."""
|
||||
yield from hass.services.async_call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
"""Set up general services related to Home Assistant."""
|
||||
@asyncio.coroutine
|
||||
def async_handle_turn_service(service):
|
||||
async def async_handle_turn_service(service):
|
||||
"""Handle calls to homeassistant.turn_on/off."""
|
||||
entity_ids = extract_entity_ids(hass, service)
|
||||
|
||||
@@ -148,7 +103,7 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
tasks.append(hass.services.async_call(
|
||||
domain, service.service, data, blocking))
|
||||
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
|
||||
@@ -164,15 +119,14 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_core_service(call):
|
||||
async def async_handle_core_service(call):
|
||||
"""Service handler for handling core services."""
|
||||
if call.service == SERVICE_HOMEASSISTANT_STOP:
|
||||
hass.async_create_task(hass.async_stop())
|
||||
return
|
||||
|
||||
try:
|
||||
errors = yield from conf_util.async_check_ha_config_file(hass)
|
||||
errors = await conf_util.async_check_ha_config_file(hass)
|
||||
except HomeAssistantError:
|
||||
return
|
||||
|
||||
@@ -186,23 +140,30 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
if call.service == SERVICE_HOMEASSISTANT_RESTART:
|
||||
hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
|
||||
|
||||
async def async_handle_update_service(call):
|
||||
"""Service handler for updating an entity."""
|
||||
await hass.helpers.entity_component.async_update_entity(
|
||||
call.data[ATTR_ENTITY_ID])
|
||||
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service,
|
||||
schema=SCHEMA_UPDATE_ENTITY)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_reload_config(call):
|
||||
async def async_handle_reload_config(call):
|
||||
"""Service handler for reloading core config."""
|
||||
try:
|
||||
conf = yield from conf_util.async_hass_config_yaml(hass)
|
||||
conf = await conf_util.async_hass_config_yaml(hass)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error(err)
|
||||
return
|
||||
|
||||
yield from conf_util.async_process_ha_core_config(
|
||||
await conf_util.async_process_ha_core_config(
|
||||
hass, conf.get(ha.DOMAIN) or {})
|
||||
|
||||
hass.services.async_register(
|
||||
|
||||
@@ -4,7 +4,6 @@ This component provides basic support for Abode Home Security system.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/abode/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from functools import partial
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
@@ -19,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.13.1']
|
||||
REQUIREMENTS = ['abodepy==0.14.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -261,8 +260,7 @@ class AbodeDevice(Entity):
|
||||
self._data = data
|
||||
self._device = device
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.add_device_callback,
|
||||
@@ -308,8 +306,7 @@ class AbodeAutomation(Entity):
|
||||
self._automation = automation
|
||||
self._event = event
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
if self._event:
|
||||
self.hass.async_add_job(
|
||||
|
||||
@@ -4,7 +4,6 @@ Component to interface with an alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@@ -14,7 +13,6 @@ 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.loader import bind_hass
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -32,85 +30,12 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_disarm(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for disarm."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_home(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm home."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_away(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm away."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_night(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm night."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_trigger(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for trigger."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_custom_bypass(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm custom bypass."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Track states and offer events for sensors."""
|
||||
component = hass.data[DOMAIN] = EntityComponent(
|
||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
||||
|
||||
yield from component.async_setup(config)
|
||||
await component.async_setup(config)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA,
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -59,8 +58,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
self._ready = None
|
||||
self._zone_bypassed = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
|
||||
@@ -4,7 +4,6 @@ Interfaces with Alarm.com alarm control panels.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
||||
@@ -32,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up a Alarm.com control panel."""
|
||||
name = config.get(CONF_NAME)
|
||||
code = config.get(CONF_CODE)
|
||||
@@ -42,7 +40,7 @@ def async_setup_platform(hass, config, async_add_entities,
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
alarmdotcom = AlarmDotCom(hass, name, code, username, password)
|
||||
yield from alarmdotcom.async_login()
|
||||
await alarmdotcom.async_login()
|
||||
async_add_entities([alarmdotcom])
|
||||
|
||||
|
||||
@@ -63,15 +61,13 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
self._alarm = Alarmdotcom(
|
||||
username, password, self._websession, hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_login(self):
|
||||
async def async_login(self):
|
||||
"""Login to Alarm.com."""
|
||||
yield from self._alarm.async_login()
|
||||
await self._alarm.async_login()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Fetch the latest state."""
|
||||
yield from self._alarm.async_update()
|
||||
await self._alarm.async_update()
|
||||
return self._alarm.state
|
||||
|
||||
@property
|
||||
@@ -106,23 +102,20 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
'sensor_status': self._alarm.sensor_status
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_disarm()
|
||||
await self._alarm.async_alarm_disarm()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm hom command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_arm_home()
|
||||
await self._alarm.async_alarm_arm_home()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_arm_away()
|
||||
await self._alarm.async_alarm_arm_away()
|
||||
|
||||
def _validate_code(self, code):
|
||||
"""Validate given code."""
|
||||
|
||||
92
homeassistant/components/alarm_control_panel/blink.py
Normal file
92
homeassistant/components/alarm_control_panel/blink.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Support for Blink Alarm Control Panel.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.blink/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.components.blink import (
|
||||
BLINK_DATA, DEFAULT_ATTRIBUTION)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
ICON = 'mdi:security'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Arlo Alarm Control Panels."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = hass.data[BLINK_DATA]
|
||||
|
||||
# Current version of blinkpy API only supports one sync module. When
|
||||
# support for additional models is added, the sync module name should
|
||||
# come from the API.
|
||||
sync_modules = []
|
||||
sync_modules.append(BlinkSyncModule(data, 'sync'))
|
||||
add_entities(sync_modules, True)
|
||||
|
||||
|
||||
class BlinkSyncModule(AlarmControlPanel):
|
||||
"""Representation of a Blink Alarm Control Panel."""
|
||||
|
||||
def __init__(self, data, name):
|
||||
"""Initialize the alarm control panel."""
|
||||
self.data = data
|
||||
self.sync = data.sync
|
||||
self._name = name
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique id for the sync module."""
|
||||
return self.sync.serial
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the panel."""
|
||||
return "{} {}".format(BLINK_DATA, self._name)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attr = self.sync.attributes
|
||||
attr['network_info'] = self.data.networks
|
||||
attr[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
|
||||
return attr
|
||||
|
||||
def update(self):
|
||||
"""Update the state of the device."""
|
||||
_LOGGER.debug("Updating Blink Alarm Control Panel %s", self._name)
|
||||
self.data.refresh()
|
||||
mode = self.sync.arm
|
||||
if mode:
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self.sync.arm = False
|
||||
self.sync.refresh()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm command."""
|
||||
self.sync.arm = True
|
||||
self.sync.refresh()
|
||||
@@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.egardia/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import requests
|
||||
@@ -61,8 +60,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
self._rs_codes = rs_codes
|
||||
self._rs_port = rs_port
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Add Egardiaserver callback if enabled."""
|
||||
if self._rs_enabled:
|
||||
_LOGGER.debug("Registering callback to Egardiaserver")
|
||||
|
||||
204
homeassistant/components/alarm_control_panel/elkm1.py
Normal file
204
homeassistant/components/alarm_control_panel/elkm1.py
Normal file
@@ -0,0 +1,204 @@
|
||||
"""
|
||||
Each ElkM1 area will be created as a separate alarm_control_panel in HASS.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.elkm1/
|
||||
"""
|
||||
|
||||
import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||
from homeassistant.components.elkm1 import (
|
||||
DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
|
||||
DEPENDENCIES = [ELK_DOMAIN]
|
||||
|
||||
SIGNAL_ARM_ENTITY = 'elkm1_arm'
|
||||
SIGNAL_DISPLAY_MESSAGE = 'elkm1_display_message'
|
||||
|
||||
ELK_ALARM_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_ENTITY_ID, default=[]): cv.entity_ids,
|
||||
vol.Required(ATTR_CODE): vol.All(vol.Coerce(int), vol.Range(0, 999999)),
|
||||
})
|
||||
|
||||
DISPLAY_MESSAGE_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID, default=[]): cv.entity_ids,
|
||||
vol.Optional('clear', default=2): vol.In([0, 1, 2]),
|
||||
vol.Optional('beep', default=False): cv.boolean,
|
||||
vol.Optional('timeout', default=0): vol.Range(min=0, max=65535),
|
||||
vol.Optional('line1', default=''): cv.string,
|
||||
vol.Optional('line2', default=''): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the ElkM1 alarm platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
elk = hass.data[ELK_DOMAIN]['elk']
|
||||
entities = create_elk_entities(hass, elk.areas, 'area', ElkArea, [])
|
||||
async_add_entities(entities, True)
|
||||
|
||||
def _dispatch(signal, entity_ids, *args):
|
||||
for entity_id in entity_ids:
|
||||
async_dispatcher_send(
|
||||
hass, '{}_{}'.format(signal, entity_id), *args)
|
||||
|
||||
def _arm_service(service):
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID, [])
|
||||
arm_level = _arm_services().get(service.service)
|
||||
args = (arm_level, service.data.get(ATTR_CODE))
|
||||
_dispatch(SIGNAL_ARM_ENTITY, entity_ids, *args)
|
||||
|
||||
for service in _arm_services():
|
||||
hass.services.async_register(
|
||||
alarm.DOMAIN, service, _arm_service, ELK_ALARM_SERVICE_SCHEMA)
|
||||
|
||||
def _display_message_service(service):
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID, [])
|
||||
data = service.data
|
||||
args = (data['clear'], data['beep'], data['timeout'],
|
||||
data['line1'], data['line2'])
|
||||
_dispatch(SIGNAL_DISPLAY_MESSAGE, entity_ids, *args)
|
||||
|
||||
hass.services.async_register(
|
||||
alarm.DOMAIN, 'elkm1_alarm_display_message',
|
||||
_display_message_service, DISPLAY_MESSAGE_SERVICE_SCHEMA)
|
||||
|
||||
|
||||
def _arm_services():
|
||||
from elkm1_lib.const import ArmLevel
|
||||
|
||||
return {
|
||||
'elkm1_alarm_arm_vacation': ArmLevel.ARMED_VACATION.value,
|
||||
'elkm1_alarm_arm_home_instant': ArmLevel.ARMED_STAY_INSTANT.value,
|
||||
'elkm1_alarm_arm_night_instant': ArmLevel.ARMED_NIGHT_INSTANT.value,
|
||||
}
|
||||
|
||||
|
||||
class ElkArea(ElkEntity, alarm.AlarmControlPanel):
|
||||
"""Representation of an Area / Partition within the ElkM1 alarm panel."""
|
||||
|
||||
def __init__(self, element, elk, elk_data):
|
||||
"""Initialize Area as Alarm Control Panel."""
|
||||
super().__init__(element, elk, elk_data)
|
||||
self._changed_by_entity_id = ''
|
||||
self._state = None
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callback for ElkM1 changes."""
|
||||
await super().async_added_to_hass()
|
||||
for keypad in self._elk.keypads:
|
||||
keypad.add_callback(self._watch_keypad)
|
||||
async_dispatcher_connect(
|
||||
self.hass, '{}_{}'.format(SIGNAL_ARM_ENTITY, self.entity_id),
|
||||
self._arm_service)
|
||||
async_dispatcher_connect(
|
||||
self.hass, '{}_{}'.format(SIGNAL_DISPLAY_MESSAGE, self.entity_id),
|
||||
self._display_message)
|
||||
|
||||
def _watch_keypad(self, keypad, changeset):
|
||||
if keypad.area != self._element.index:
|
||||
return
|
||||
if changeset.get('last_user') is not None:
|
||||
self._changed_by_entity_id = self.hass.data[
|
||||
ELK_DOMAIN]['keypads'].get(keypad.index, '')
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the alarm code format."""
|
||||
return '^[0-9]{4}([0-9]{2})?$'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the element."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Attributes of the area."""
|
||||
from elkm1_lib.const import AlarmState, ArmedStatus, ArmUpState
|
||||
|
||||
attrs = self.initial_attrs()
|
||||
elmt = self._element
|
||||
attrs['is_exit'] = elmt.is_exit
|
||||
attrs['timer1'] = elmt.timer1
|
||||
attrs['timer2'] = elmt.timer2
|
||||
if elmt.armed_status is not None:
|
||||
attrs['armed_status'] = \
|
||||
ArmedStatus(elmt.armed_status).name.lower()
|
||||
if elmt.arm_up_state is not None:
|
||||
attrs['arm_up_state'] = ArmUpState(elmt.arm_up_state).name.lower()
|
||||
if elmt.alarm_state is not None:
|
||||
attrs['alarm_state'] = AlarmState(elmt.alarm_state).name.lower()
|
||||
attrs['changed_by_entity_id'] = self._changed_by_entity_id
|
||||
return attrs
|
||||
|
||||
def _element_changed(self, element, changeset):
|
||||
from elkm1_lib.const import ArmedStatus
|
||||
|
||||
elk_state_to_hass_state = {
|
||||
ArmedStatus.DISARMED.value: STATE_ALARM_DISARMED,
|
||||
ArmedStatus.ARMED_AWAY.value: STATE_ALARM_ARMED_AWAY,
|
||||
ArmedStatus.ARMED_STAY.value: STATE_ALARM_ARMED_HOME,
|
||||
ArmedStatus.ARMED_STAY_INSTANT.value: STATE_ALARM_ARMED_HOME,
|
||||
ArmedStatus.ARMED_TO_NIGHT.value: STATE_ALARM_ARMED_NIGHT,
|
||||
ArmedStatus.ARMED_TO_NIGHT_INSTANT.value: STATE_ALARM_ARMED_NIGHT,
|
||||
ArmedStatus.ARMED_TO_VACATION.value: STATE_ALARM_ARMED_AWAY,
|
||||
}
|
||||
|
||||
if self._element.alarm_state is None:
|
||||
self._state = None
|
||||
elif self._area_is_in_alarm_state():
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif self._entry_exit_timer_is_running():
|
||||
self._state = STATE_ALARM_ARMING \
|
||||
if self._element.is_exit else STATE_ALARM_PENDING
|
||||
else:
|
||||
self._state = elk_state_to_hass_state[self._element.armed_status]
|
||||
|
||||
def _entry_exit_timer_is_running(self):
|
||||
return self._element.timer1 > 0 or self._element.timer2 > 0
|
||||
|
||||
def _area_is_in_alarm_state(self):
|
||||
from elkm1_lib.const import AlarmState
|
||||
|
||||
return self._element.alarm_state >= AlarmState.FIRE_ALARM.value
|
||||
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._element.disarm(int(code))
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
from elkm1_lib.const import ArmLevel
|
||||
|
||||
self._element.arm(ArmLevel.ARMED_STAY.value, int(code))
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
from elkm1_lib.const import ArmLevel
|
||||
|
||||
self._element.arm(ArmLevel.ARMED_AWAY.value, int(code))
|
||||
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
from elkm1_lib.const import ArmLevel
|
||||
|
||||
self._element.arm(ArmLevel.ARMED_NIGHT.value, int(code))
|
||||
|
||||
async def _arm_service(self, arm_level, code):
|
||||
self._element.arm(arm_level, code)
|
||||
|
||||
async def _display_message(self, clear, beep, timeout, line1, line2):
|
||||
"""Display a message on all keypads for the area."""
|
||||
self._element.display_message(clear, beep, timeout, line1, line2)
|
||||
@@ -4,7 +4,6 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.envisalink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -32,9 +31,8 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Perform the setup for Envisalink alarm panels."""
|
||||
configured_partitions = discovery_info['partitions']
|
||||
code = discovery_info[CONF_CODE]
|
||||
@@ -88,8 +86,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
_LOGGER.debug("Setting up alarm: %s", alarm_name)
|
||||
super().__init__(alarm_name, info, controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
|
||||
@@ -128,8 +125,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
state = STATE_ALARM_DISARMED
|
||||
return state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].disarm_partition(
|
||||
@@ -138,8 +134,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].disarm_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
||||
@@ -148,8 +143,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].arm_away_partition(
|
||||
@@ -158,8 +152,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].arm_away_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_trigger(self, code=None):
|
||||
async def async_alarm_trigger(self, code=None):
|
||||
"""Alarm trigger command. Will be used to trigger a panic alarm."""
|
||||
self.hass.data[DATA_EVL].panic_alarm(self._panic_type)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for manual alarms controllable via MQTT.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
@@ -363,8 +362,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
return mqtt.async_subscribe(
|
||||
self.hass, self._command_topic, message_received, self._qos)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_state_changed_listener(self, entity_id, old_state, new_state):
|
||||
async def _async_state_changed_listener(self, entity_id, old_state,
|
||||
new_state):
|
||||
"""Publish state change to MQTT."""
|
||||
mqtt.async_publish(
|
||||
self.hass, self._state_topic, new_state.state, self._qos, True)
|
||||
|
||||
@@ -4,7 +4,6 @@ This platform enables the possibility to control a MQTT alarm.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
||||
@@ -18,10 +17,13 @@ from homeassistant.const import (
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
|
||||
CONF_NAME, CONF_CODE)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
|
||||
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
|
||||
CONF_RETAIN, MqttAvailability)
|
||||
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
|
||||
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
|
||||
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
|
||||
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -46,13 +48,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the MQTT Alarm Control Panel platform."""
|
||||
if discovery_info is not None:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_entities, discovery_info=None):
|
||||
"""Set up MQTT alarm control panel 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 alarm control panel dynamically through MQTT discovery."""
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add an MQTT alarm control panel."""
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_payload[ATTR_DISCOVERY_HASH])
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format(alarm.DOMAIN, 'mqtt'),
|
||||
async_discover)
|
||||
|
||||
|
||||
async def _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_hash=None):
|
||||
"""Set up the MQTT Alarm Control Panel platform."""
|
||||
async_add_entities([MqttAlarm(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
@@ -65,18 +82,22 @@ def async_setup_platform(hass, config, async_add_entities,
|
||||
config.get(CONF_CODE),
|
||||
config.get(CONF_AVAILABILITY_TOPIC),
|
||||
config.get(CONF_PAYLOAD_AVAILABLE),
|
||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE))])
|
||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
||||
discovery_hash,)])
|
||||
|
||||
|
||||
class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
|
||||
alarm.AlarmControlPanel):
|
||||
"""Representation of a MQTT alarm status."""
|
||||
|
||||
def __init__(self, name, state_topic, command_topic, qos, retain,
|
||||
payload_disarm, payload_arm_home, payload_arm_away, code,
|
||||
availability_topic, payload_available, payload_not_available):
|
||||
availability_topic, payload_available, payload_not_available,
|
||||
discovery_hash):
|
||||
"""Init the MQTT Alarm Control Panel."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
MqttAvailability.__init__(self, availability_topic, qos,
|
||||
payload_available, payload_not_available)
|
||||
MqttDiscoveryUpdate.__init__(self, discovery_hash)
|
||||
self._state = STATE_UNKNOWN
|
||||
self._name = name
|
||||
self._state_topic = state_topic
|
||||
@@ -87,11 +108,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self._payload_arm_home = payload_arm_home
|
||||
self._payload_arm_away = payload_arm_away
|
||||
self._code = code
|
||||
self._discovery_hash = discovery_hash
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe mqtt events."""
|
||||
yield from super().async_added_to_hass()
|
||||
await MqttAvailability.async_added_to_hass(self)
|
||||
await MqttDiscoveryUpdate.async_added_to_hass(self)
|
||||
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
@@ -104,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self._state = payload
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
yield from mqtt.async_subscribe(
|
||||
await mqtt.async_subscribe(
|
||||
self.hass, self._state_topic, message_received, self._qos)
|
||||
|
||||
@property
|
||||
@@ -131,8 +153,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -143,8 +164,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self.hass, self._command_topic, self._payload_disarm, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -155,8 +175,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self.hass, self._command_topic, self._payload_arm_home, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command.
|
||||
|
||||
This method is a coroutine.
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for Satel Integra alarm, using ETHM module: https://www.satel.pl/en/ .
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.satel_integra/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
@@ -18,9 +17,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['satel_integra']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up for Satel Integra alarm panels."""
|
||||
if not discovery_info:
|
||||
return
|
||||
@@ -39,8 +37,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
self._state = None
|
||||
self._arm_home_mode = arm_home_mode
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
@@ -74,21 +71,18 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].disarm(code)
|
||||
await self.hass.data[DATA_SATEL].disarm(code)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(code)
|
||||
await self.hass.data[DATA_SATEL].arm(code)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(
|
||||
await self.hass.data[DATA_SATEL].arm(
|
||||
code, self._arm_home_mode)
|
||||
|
||||
@@ -79,3 +79,55 @@ ifttt_push_alarm_state:
|
||||
state:
|
||||
description: The state to which the alarm control panel has to be set.
|
||||
example: 'armed_night'
|
||||
|
||||
elkm1_alarm_arm_vacation:
|
||||
description: Arm the ElkM1 in vacation mode.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm.
|
||||
example: 'alarm_control_panel.main'
|
||||
code:
|
||||
description: An code to arm the alarm control panel.
|
||||
example: 1234
|
||||
|
||||
elkm1_alarm_arm_home_instant:
|
||||
description: Arm the ElkM1 in home instant mode.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm.
|
||||
example: 'alarm_control_panel.main'
|
||||
code:
|
||||
description: An code to arm the alarm control panel.
|
||||
example: 1234
|
||||
|
||||
elkm1_alarm_arm_night_instant:
|
||||
description: Arm the ElkM1 in night instant mode.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm.
|
||||
example: 'alarm_control_panel.main'
|
||||
code:
|
||||
description: An code to arm the alarm control panel.
|
||||
example: 1234
|
||||
|
||||
elkm1_alarm_display_message:
|
||||
description: Display a message on all of the ElkM1 keypads for an area.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to display messages on.
|
||||
example: 'alarm_control_panel.main'
|
||||
clear:
|
||||
description: 0=clear message, 1=clear message with * key, 2=Display until timeout; default 2
|
||||
example: 1
|
||||
beep:
|
||||
description: 0=no beep, 1=beep; default 0
|
||||
example: 1
|
||||
timeout:
|
||||
description: Time to display message, 0=forever, max 65535, default 0
|
||||
example: 4242
|
||||
line1:
|
||||
description: Up to 16 characters of text (truncated if too long). Default blank.
|
||||
example: The answer to life,
|
||||
line2:
|
||||
description: Up to 16 characters of text (truncated if too long). Default blank.
|
||||
example: the universe, and everything.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Interfaces with SimpliSafe alarm control panel.
|
||||
This platform provides alarm control functionality for SimpliSafe.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.simplisafe/
|
||||
@@ -7,80 +7,61 @@ https://home-assistant.io/components/alarm_control_panel.simplisafe/
|
||||
import logging
|
||||
import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
PLATFORM_SCHEMA, AlarmControlPanel)
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.components.simplisafe.const import (
|
||||
DATA_CLIENT, DOMAIN, TOPIC_UPDATE)
|
||||
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)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==2.0.2']
|
||||
CONF_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'SimpliSafe'
|
||||
|
||||
ATTR_ALARM_ACTIVE = "alarm_active"
|
||||
ATTR_TEMPERATURE = "temperature"
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_CODE): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
ATTR_ALARM_ACTIVE = 'alarm_active'
|
||||
ATTR_TEMPERATURE = 'temperature'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the SimpliSafe platform."""
|
||||
from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException
|
||||
name = config.get(CONF_NAME)
|
||||
code = config.get(CONF_CODE)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up a SimpliSafe alarm control panel based on existing config."""
|
||||
pass
|
||||
|
||||
try:
|
||||
simplisafe = SimpliSafeApiInterface(username, password)
|
||||
except SimpliSafeAPIException:
|
||||
_LOGGER.error("Failed to set up SimpliSafe")
|
||||
return
|
||||
|
||||
systems = []
|
||||
|
||||
for system in simplisafe.get_systems():
|
||||
systems.append(SimpliSafeAlarm(system, name, code))
|
||||
|
||||
add_entities(systems)
|
||||
async def async_setup_entry(hass, entry, async_add_entities):
|
||||
"""Set up a SimpliSafe alarm control panel based on a config entry."""
|
||||
systems = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||
async_add_entities([
|
||||
SimpliSafeAlarm(system, entry.data.get(CONF_CODE))
|
||||
for system in systems
|
||||
], True)
|
||||
|
||||
|
||||
class SimpliSafeAlarm(AlarmControlPanel):
|
||||
"""Representation of a SimpliSafe alarm."""
|
||||
|
||||
def __init__(self, simplisafe, name, code):
|
||||
def __init__(self, system, code):
|
||||
"""Initialize the SimpliSafe alarm."""
|
||||
self.simplisafe = simplisafe
|
||||
self._name = name
|
||||
self._code = str(code) if code else None
|
||||
self._async_unsub_dispatcher_connect = None
|
||||
self._attrs = {}
|
||||
self._code = code
|
||||
self._system = system
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID."""
|
||||
return self.simplisafe.location_id
|
||||
return self._system.system_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
if self._name is not None:
|
||||
return self._name
|
||||
return 'Alarm {}'.format(self.simplisafe.location_id)
|
||||
return self._system.address
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
if self._code is None:
|
||||
if not self._code:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
@@ -89,53 +70,12 @@ class SimpliSafeAlarm(AlarmControlPanel):
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
status = self.simplisafe.state
|
||||
if status.lower() == 'off':
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif status.lower() == 'home' or status.lower() == 'home_count':
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif (status.lower() == 'away' or status.lower() == 'exitDelay' or
|
||||
status.lower() == 'away_count'):
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
return state
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attributes = {}
|
||||
|
||||
attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active
|
||||
if self.simplisafe.temperature is not None:
|
||||
attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature
|
||||
|
||||
return attributes
|
||||
|
||||
def update(self):
|
||||
"""Update alarm status."""
|
||||
self.simplisafe.update()
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
self.simplisafe.set_state('off')
|
||||
_LOGGER.info("SimpliSafe alarm disarming")
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
self.simplisafe.set_state('home')
|
||||
_LOGGER.info("SimpliSafe alarm arming home")
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
self.simplisafe.set_state('away')
|
||||
_LOGGER.info("SimpliSafe alarm arming away")
|
||||
return self._attrs
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
"""Validate given code."""
|
||||
@@ -143,3 +83,63 @@ class SimpliSafeAlarm(AlarmControlPanel):
|
||||
if not check:
|
||||
_LOGGER.warning("Wrong code entered for %s", state)
|
||||
return check
|
||||
|
||||
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) -> None:
|
||||
"""Disconnect dispatcher listener when removed."""
|
||||
if self._async_unsub_dispatcher_connect:
|
||||
self._async_unsub_dispatcher_connect()
|
||||
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
|
||||
await self._system.set_off()
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
|
||||
await self._system.set_home()
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
|
||||
await self._system.set_away()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update alarm status."""
|
||||
from simplipy.system import SystemStates
|
||||
|
||||
await self._system.update()
|
||||
|
||||
self._attrs[ATTR_ALARM_ACTIVE] = self._system.alarm_going_off
|
||||
if self._system.temperature:
|
||||
self._attrs[ATTR_TEMPERATURE] = self._system.temperature
|
||||
|
||||
if self._system.state == SystemStates.error:
|
||||
return
|
||||
|
||||
if self._system.state == SystemStates.off:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif self._system.state in (SystemStates.home,
|
||||
SystemStates.home_count):
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif self._system.state in (SystemStates.away, SystemStates.away_count,
|
||||
SystemStates.exit_delay):
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
self._state = None
|
||||
|
||||
@@ -4,71 +4,63 @@ Support for Vanderbilt (formerly Siemens) SPC alarm systems.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.spc/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.spc import (
|
||||
ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_ALARM)
|
||||
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_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPC_AREA_MODE_TO_STATE = {
|
||||
'0': STATE_ALARM_DISARMED,
|
||||
'1': STATE_ALARM_ARMED_HOME,
|
||||
'3': STATE_ALARM_ARMED_AWAY,
|
||||
}
|
||||
|
||||
|
||||
def _get_alarm_state(spc_mode):
|
||||
def _get_alarm_state(area):
|
||||
"""Get the alarm state."""
|
||||
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
|
||||
from pyspcwebgw.const import AreaMode
|
||||
|
||||
if area.verified_alarm:
|
||||
return STATE_ALARM_TRIGGERED
|
||||
|
||||
mode_to_state = {
|
||||
AreaMode.UNSET: STATE_ALARM_DISARMED,
|
||||
AreaMode.PART_SET_A: STATE_ALARM_ARMED_HOME,
|
||||
AreaMode.PART_SET_B: STATE_ALARM_ARMED_NIGHT,
|
||||
AreaMode.FULL_SET: STATE_ALARM_ARMED_AWAY,
|
||||
}
|
||||
return mode_to_state.get(area.mode)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the SPC alarm control panel platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_AREAS] is None):
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
api = hass.data[DATA_API]
|
||||
devices = [SpcAlarm(api, area)
|
||||
for area in discovery_info[ATTR_DISCOVER_AREAS]]
|
||||
|
||||
async_add_entities(devices)
|
||||
async_add_entities([SpcAlarm(area=area, api=api)
|
||||
for area in api.areas.values()])
|
||||
|
||||
|
||||
class SpcAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of the SPC alarm panel."""
|
||||
|
||||
def __init__(self, api, area):
|
||||
def __init__(self, area, api):
|
||||
"""Initialize the SPC alarm panel."""
|
||||
self._area_id = area['id']
|
||||
self._name = area['name']
|
||||
self._state = _get_alarm_state(area['mode'])
|
||||
if self._state == STATE_ALARM_DISARMED:
|
||||
self._changed_by = area.get('last_unset_user_name', 'unknown')
|
||||
else:
|
||||
self._changed_by = area.get('last_set_user_name', 'unknown')
|
||||
self._area = area
|
||||
self._api = api
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call for adding new entities."""
|
||||
self.hass.data[DATA_REGISTRY].register_alarm_device(
|
||||
self._area_id, self)
|
||||
async_dispatcher_connect(self.hass,
|
||||
SIGNAL_UPDATE_ALARM.format(self._area.id),
|
||||
self._update_callback)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update_from_spc(self, state, extra):
|
||||
"""Update the alarm panel with a new state."""
|
||||
self._state = state
|
||||
self._changed_by = extra.get('changed_by', 'unknown')
|
||||
self.async_schedule_update_ha_state()
|
||||
@callback
|
||||
def _update_callback(self):
|
||||
"""Call update method."""
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@@ -78,32 +70,38 @@ class SpcAlarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
return self._area.name
|
||||
|
||||
@property
|
||||
def changed_by(self):
|
||||
"""Return the user the last change was triggered by."""
|
||||
return self._changed_by
|
||||
return self._area.last_changed_by
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
return _get_alarm_state(self._area)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_UNSET)
|
||||
from pyspcwebgw.const import AreaMode
|
||||
await self._api.change_mode(area=self._area,
|
||||
new_mode=AreaMode.UNSET)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_PART_SET)
|
||||
from pyspcwebgw.const import AreaMode
|
||||
await self._api.change_mode(area=self._area,
|
||||
new_mode=AreaMode.PART_SET_A)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm home command."""
|
||||
from pyspcwebgw.const import AreaMode
|
||||
await self._api.change_mode(area=self._area,
|
||||
new_mode=AreaMode.PART_SET_B)
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_SET)
|
||||
from pyspcwebgw.const import AreaMode
|
||||
await self._api.change_mode(area=self._area,
|
||||
new_mode=AreaMode.FULL_SET)
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS)
|
||||
|
||||
|
||||
REQUIREMENTS = ['total_connect_client==0.18']
|
||||
REQUIREMENTS = ['total_connect_client==0.20']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Interfaces with Wink Cameras.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.wink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
@@ -38,8 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
"""Representation a Wink camera alarm."""
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_MESSAGE, 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)
|
||||
@@ -59,53 +60,12 @@ def is_on(hass, entity_id):
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
def turn_on(hass, entity_id):
|
||||
"""Reset the alert."""
|
||||
hass.add_job(async_turn_on, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_turn_on(hass, entity_id):
|
||||
"""Async reset the alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
|
||||
|
||||
|
||||
def turn_off(hass, entity_id):
|
||||
"""Acknowledge alert."""
|
||||
hass.add_job(async_turn_off, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_turn_off(hass, entity_id):
|
||||
"""Async acknowledge the alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
|
||||
|
||||
|
||||
def toggle(hass, entity_id):
|
||||
"""Toggle acknowledgement of alert."""
|
||||
hass.add_job(async_toggle, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_toggle(hass, entity_id):
|
||||
"""Async toggle acknowledgement of alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Alert component."""
|
||||
alerts = config.get(DOMAIN)
|
||||
all_alerts = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_alert_service(service_call):
|
||||
async def async_handle_alert_service(service_call):
|
||||
"""Handle calls to alert services."""
|
||||
alert_ids = service.extract_entity_ids(hass, service_call)
|
||||
|
||||
@@ -113,11 +73,11 @@ def async_setup(hass, config):
|
||||
alert = all_alerts[alert_id]
|
||||
alert.async_set_context(service_call.context)
|
||||
if service_call.service == SERVICE_TURN_ON:
|
||||
yield from alert.async_turn_on()
|
||||
await alert.async_turn_on()
|
||||
elif service_call.service == SERVICE_TOGGLE:
|
||||
yield from alert.async_toggle()
|
||||
await alert.async_toggle()
|
||||
else:
|
||||
yield from alert.async_turn_off()
|
||||
await alert.async_turn_off()
|
||||
|
||||
# Setup alerts
|
||||
for entity_id, alert in alerts.items():
|
||||
@@ -141,7 +101,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
return True
|
||||
|
||||
@@ -196,17 +156,15 @@ class Alert(ToggleEntity):
|
||||
"""Hide the alert when it is not firing."""
|
||||
return not self._can_ack or not self._firing
|
||||
|
||||
@asyncio.coroutine
|
||||
def watched_entity_change(self, entity, from_state, to_state):
|
||||
async def watched_entity_change(self, entity, from_state, to_state):
|
||||
"""Determine if the alert should start or stop."""
|
||||
_LOGGER.debug("Watched entity (%s) has changed", entity)
|
||||
if to_state.state == self._alert_state and not self._firing:
|
||||
yield from self.begin_alerting()
|
||||
await self.begin_alerting()
|
||||
if to_state.state != self._alert_state and self._firing:
|
||||
yield from self.end_alerting()
|
||||
await self.end_alerting()
|
||||
|
||||
@asyncio.coroutine
|
||||
def begin_alerting(self):
|
||||
async def begin_alerting(self):
|
||||
"""Begin the alert procedures."""
|
||||
_LOGGER.debug("Beginning Alert: %s", self._name)
|
||||
self._ack = False
|
||||
@@ -214,25 +172,23 @@ class Alert(ToggleEntity):
|
||||
self._next_delay = 0
|
||||
|
||||
if not self._skip_first:
|
||||
yield from self._notify()
|
||||
await self._notify()
|
||||
else:
|
||||
yield from self._schedule_notify()
|
||||
await self._schedule_notify()
|
||||
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def end_alerting(self):
|
||||
async def end_alerting(self):
|
||||
"""End the alert procedures."""
|
||||
_LOGGER.debug("Ending Alert: %s", self._name)
|
||||
self._cancel()
|
||||
self._ack = False
|
||||
self._firing = False
|
||||
if self._done_message and self._send_done_message:
|
||||
yield from self._notify_done_message()
|
||||
await self._notify_done_message()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _schedule_notify(self):
|
||||
async def _schedule_notify(self):
|
||||
"""Schedule a notification."""
|
||||
delay = self._delay[self._next_delay]
|
||||
next_msg = datetime.now() + delay
|
||||
@@ -240,8 +196,7 @@ class Alert(ToggleEntity):
|
||||
event.async_track_point_in_time(self.hass, self._notify, next_msg)
|
||||
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _notify(self, *args):
|
||||
async def _notify(self, *args):
|
||||
"""Send the alert notification."""
|
||||
if not self._firing:
|
||||
return
|
||||
@@ -250,36 +205,32 @@ class Alert(ToggleEntity):
|
||||
_LOGGER.info("Alerting: %s", self._name)
|
||||
self._send_done_message = True
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._name})
|
||||
yield from self._schedule_notify()
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name})
|
||||
await self._schedule_notify()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _notify_done_message(self, *args):
|
||||
async def _notify_done_message(self, *args):
|
||||
"""Send notification of complete alert."""
|
||||
_LOGGER.info("Alerting: %s", self._done_message)
|
||||
self._send_done_message = False
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._done_message})
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message})
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Async Unacknowledge alert."""
|
||||
_LOGGER.debug("Reset Alert: %s", self._name)
|
||||
self._ack = False
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Async Acknowledge alert."""
|
||||
_LOGGER.debug("Acknowledged Alert: %s", self._name)
|
||||
self._ack = True
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_toggle(self, **kwargs):
|
||||
async def async_toggle(self, **kwargs):
|
||||
"""Async toggle alert."""
|
||||
if self._ack:
|
||||
return self.async_turn_on()
|
||||
return self.async_turn_off()
|
||||
return await self.async_turn_on()
|
||||
return await self.async_turn_off()
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for Alexa skill service end point.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -53,8 +52,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Activate Alexa component."""
|
||||
config = config.get(DOMAIN, {})
|
||||
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for Alexa skill service end point.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
|
||||
@@ -59,16 +58,15 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
url = INTENTS_API_ENDPOINT
|
||||
name = 'api:alexa'
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
async def post(self, request):
|
||||
"""Handle Alexa."""
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
message = await request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa request: %s", message)
|
||||
|
||||
try:
|
||||
response = yield from async_handle_message(hass, message)
|
||||
response = await async_handle_message(hass, message)
|
||||
return b'' if response is None else self.json(response)
|
||||
except UnknownRequest as err:
|
||||
_LOGGER.warning(str(err))
|
||||
@@ -101,8 +99,7 @@ def intent_error_response(hass, message, error):
|
||||
return alexa_response.as_dict()
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_message(hass, message):
|
||||
async def async_handle_message(hass, message):
|
||||
"""Handle an Alexa intent.
|
||||
|
||||
Raises:
|
||||
@@ -120,20 +117,18 @@ def async_handle_message(hass, message):
|
||||
if not handler:
|
||||
raise UnknownRequest('Received unknown request {}'.format(req_type))
|
||||
|
||||
return (yield from handler(hass, message))
|
||||
return await handler(hass, message)
|
||||
|
||||
|
||||
@HANDLERS.register('SessionEndedRequest')
|
||||
@asyncio.coroutine
|
||||
def async_handle_session_end(hass, message):
|
||||
async def async_handle_session_end(hass, message):
|
||||
"""Handle a session end request."""
|
||||
return None
|
||||
|
||||
|
||||
@HANDLERS.register('IntentRequest')
|
||||
@HANDLERS.register('LaunchRequest')
|
||||
@asyncio.coroutine
|
||||
def async_handle_intent(hass, message):
|
||||
async def async_handle_intent(hass, message):
|
||||
"""Handle an intent request.
|
||||
|
||||
Raises:
|
||||
@@ -153,7 +148,7 @@ def async_handle_intent(hass, message):
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
|
||||
intent_response = yield from intent.async_handle(
|
||||
intent_response = await intent.async_handle(
|
||||
hass, DOMAIN, intent_name,
|
||||
{key: {'value': value} for key, value
|
||||
in alexa_response.variables.items()})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Support for alexa Smart Home Skill API."""
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from datetime import datetime
|
||||
@@ -695,8 +694,7 @@ class SmartHomeView(http.HomeAssistantView):
|
||||
"""Initialize."""
|
||||
self.smart_home_config = smart_home_config
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
async def post(self, request):
|
||||
"""Handle Alexa Smart Home requests.
|
||||
|
||||
The Smart Home API requires the endpoint to be implemented in AWS
|
||||
@@ -704,11 +702,11 @@ class SmartHomeView(http.HomeAssistantView):
|
||||
the response.
|
||||
"""
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
message = await request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
|
||||
|
||||
response = yield from async_handle_message(
|
||||
response = await async_handle_message(
|
||||
hass, self.smart_home_config, message)
|
||||
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
|
||||
return b'' if response is None else self.json(response)
|
||||
@@ -1529,3 +1527,8 @@ async def async_api_reportstate(hass, config, request, context, entity):
|
||||
name='StateReport',
|
||||
context={'properties': properties}
|
||||
)
|
||||
|
||||
|
||||
def turned_off_response(message):
|
||||
"""Return a device turned off response."""
|
||||
return api_error(message[API_DIRECTIVE], error_type='BRIDGE_UNREACHABLE')
|
||||
|
||||
@@ -149,16 +149,14 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the IP Webcam component."""
|
||||
from pydroid_ipcam import PyDroidIPCam
|
||||
|
||||
webcams = hass.data[DATA_IP_WEBCAM] = {}
|
||||
websession = async_get_clientsession(hass)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_ipcamera(cam_config):
|
||||
async def async_setup_ipcamera(cam_config):
|
||||
"""Set up an IP camera."""
|
||||
host = cam_config[CONF_HOST]
|
||||
username = cam_config.get(CONF_USERNAME)
|
||||
@@ -188,16 +186,15 @@ def async_setup(hass, config):
|
||||
if motion is None:
|
||||
motion = 'motion_active' in cam.enabled_sensors
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update_data(now):
|
||||
async def async_update_data(now):
|
||||
"""Update data from IP camera in SCAN_INTERVAL."""
|
||||
yield from cam.update()
|
||||
await cam.update()
|
||||
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
|
||||
|
||||
async_track_point_in_utc_time(
|
||||
hass, async_update_data, utcnow() + interval)
|
||||
|
||||
yield from async_update_data(None)
|
||||
await async_update_data(None)
|
||||
|
||||
# Load platforms
|
||||
webcams[host] = cam
|
||||
@@ -242,7 +239,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
return True
|
||||
|
||||
@@ -255,8 +252,7 @@ class AndroidIPCamEntity(Entity):
|
||||
self._host = host
|
||||
self._ipcam = ipcam
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register update dispatcher."""
|
||||
@callback
|
||||
def async_ipcam_update(host):
|
||||
|
||||
@@ -49,10 +49,9 @@ def setup(hass, config):
|
||||
|
||||
# It doesn't really matter why we're not able to get the status, just that
|
||||
# we can't.
|
||||
# pylint: disable=broad-except
|
||||
try:
|
||||
DATA.update(no_throttle=True)
|
||||
except Exception:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Failure while testing APCUPSd status retrieval.")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -141,6 +141,8 @@ class APIEventStream(HomeAssistantView):
|
||||
_LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj))
|
||||
unsub_stream()
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class APIConfigView(HomeAssistantView):
|
||||
"""View to handle Configuration requests."""
|
||||
|
||||
@@ -6,7 +6,6 @@ https://home-assistant.io/components/apple_tv/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from typing import Sequence, TypeVar, Union
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -78,14 +77,13 @@ def request_configuration(hass, config, atv, credentials):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = hass.components.configurator
|
||||
|
||||
@asyncio.coroutine
|
||||
def configuration_callback(callback_data):
|
||||
async def configuration_callback(callback_data):
|
||||
"""Handle the submitted configuration."""
|
||||
from pyatv import exceptions
|
||||
pin = callback_data.get('pin')
|
||||
|
||||
try:
|
||||
yield from atv.airplay.finish_authentication(pin)
|
||||
await atv.airplay.finish_authentication(pin)
|
||||
hass.components.persistent_notification.async_create(
|
||||
'Authentication succeeded!<br /><br />Add the following '
|
||||
'to credentials: in your apple_tv configuration:<br /><br />'
|
||||
@@ -109,11 +107,10 @@ def request_configuration(hass, config, atv, credentials):
|
||||
)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def scan_for_apple_tvs(hass):
|
||||
async def scan_for_apple_tvs(hass):
|
||||
"""Scan for devices and present a notification of the ones found."""
|
||||
import pyatv
|
||||
atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
|
||||
atvs = await pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
|
||||
|
||||
devices = []
|
||||
for atv in atvs:
|
||||
@@ -133,14 +130,12 @@ def scan_for_apple_tvs(hass):
|
||||
notification_id=NOTIFICATION_SCAN_ID)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Apple TV component."""
|
||||
if DATA_APPLE_TV not in hass.data:
|
||||
hass.data[DATA_APPLE_TV] = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_service_handler(service):
|
||||
async def async_service_handler(service):
|
||||
"""Handle service calls."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
@@ -159,17 +154,16 @@ def async_setup(hass, config):
|
||||
continue
|
||||
|
||||
atv = device.atv
|
||||
credentials = yield from atv.airplay.generate_credentials()
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
credentials = await atv.airplay.generate_credentials()
|
||||
await atv.airplay.load_credentials(credentials)
|
||||
_LOGGER.debug('Generated new credentials: %s', credentials)
|
||||
yield from atv.airplay.start_authentication()
|
||||
await atv.airplay.start_authentication()
|
||||
hass.async_add_job(request_configuration,
|
||||
hass, config, atv, credentials)
|
||||
|
||||
@asyncio.coroutine
|
||||
def atv_discovered(service, info):
|
||||
async def atv_discovered(service, info):
|
||||
"""Set up an Apple TV that was auto discovered."""
|
||||
yield from _setup_atv(hass, {
|
||||
await _setup_atv(hass, {
|
||||
CONF_NAME: info['name'],
|
||||
CONF_HOST: info['host'],
|
||||
CONF_LOGIN_ID: info['properties']['hG'],
|
||||
@@ -180,7 +174,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SCAN, async_service_handler,
|
||||
@@ -193,8 +187,7 @@ def async_setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _setup_atv(hass, atv_config):
|
||||
async def _setup_atv(hass, atv_config):
|
||||
"""Set up an Apple TV."""
|
||||
import pyatv
|
||||
name = atv_config.get(CONF_NAME)
|
||||
@@ -210,7 +203,7 @@ def _setup_atv(hass, atv_config):
|
||||
session = async_get_clientsession(hass)
|
||||
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
|
||||
if credentials:
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
await atv.airplay.load_credentials(credentials)
|
||||
|
||||
power = AppleTVPowerManager(hass, atv, start_off)
|
||||
hass.data[DATA_APPLE_TV][host] = {
|
||||
@@ -259,4 +252,4 @@ class AppleTVPowerManager:
|
||||
self.atv.push_updater.start()
|
||||
|
||||
for listener in self.listeners:
|
||||
self.hass.async_add_job(listener.async_update_ha_state())
|
||||
self.hass.async_create_task(listener.async_update_ha_state())
|
||||
|
||||
95
homeassistant/components/aqualogic.py
Normal file
95
homeassistant/components/aqualogic.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Support for AquaLogic component.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/aqualogic/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (CONF_HOST, CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
REQUIREMENTS = ["aqualogic==1.0"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = "aqualogic"
|
||||
UPDATE_TOPIC = DOMAIN + "_update"
|
||||
CONF_UNIT = "unit"
|
||||
RECONNECT_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT): cv.port
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up AquaLogic platform."""
|
||||
host = config[DOMAIN][CONF_HOST]
|
||||
port = config[DOMAIN][CONF_PORT]
|
||||
processor = AquaLogicProcessor(hass, host, port)
|
||||
hass.data[DOMAIN] = processor
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
|
||||
processor.start_listen)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
processor.shutdown)
|
||||
_LOGGER.debug("AquaLogicProcessor %s:%i initialized", host, port)
|
||||
return True
|
||||
|
||||
|
||||
class AquaLogicProcessor(threading.Thread):
|
||||
"""AquaLogic event processor thread."""
|
||||
|
||||
def __init__(self, hass, host, port):
|
||||
"""Initialize the data object."""
|
||||
super().__init__(daemon=True)
|
||||
self._hass = hass
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._shutdown = False
|
||||
self._panel = None
|
||||
|
||||
def start_listen(self, event):
|
||||
"""Start event-processing thread."""
|
||||
_LOGGER.debug("Event processing thread started")
|
||||
self.start()
|
||||
|
||||
def shutdown(self, event):
|
||||
"""Signal shutdown of processing event."""
|
||||
_LOGGER.debug("Event processing signaled exit")
|
||||
self._shutdown = True
|
||||
|
||||
def data_changed(self, panel):
|
||||
"""Aqualogic data changed callback."""
|
||||
self._hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC)
|
||||
|
||||
def run(self):
|
||||
"""Event thread."""
|
||||
from aqualogic.core import AquaLogic
|
||||
|
||||
while True:
|
||||
self._panel = AquaLogic()
|
||||
self._panel.connect(self._host, self._port)
|
||||
self._panel.process(self.data_changed)
|
||||
|
||||
if self._shutdown:
|
||||
return
|
||||
|
||||
_LOGGER.error("Connection to %s:%d lost",
|
||||
self._host, self._port)
|
||||
time.sleep(RECONNECT_INTERVAL.seconds)
|
||||
|
||||
@property
|
||||
def panel(self):
|
||||
"""Retrieve the AquaLogic object."""
|
||||
return self._panel
|
||||
@@ -16,7 +16,7 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
REQUIREMENTS = ['pyarlo==0.2.0']
|
||||
REQUIREMENTS = ['pyarlo==0.2.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -81,7 +81,7 @@ def setup(hass, config):
|
||||
|
||||
def hub_refresh(event_time):
|
||||
"""Call ArloHub to refresh information."""
|
||||
_LOGGER.info("Updating Arlo Hub component")
|
||||
_LOGGER.debug("Updating Arlo Hub component")
|
||||
hass.data[DATA_ARLO].update(update_cameras=True,
|
||||
update_base_station=True)
|
||||
dispatcher_send(hass, SIGNAL_UPDATE_ARLO)
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
async_dispatcher_send, dispatcher_connect)
|
||||
|
||||
REQUIREMENTS = ['asterisk_mbox==0.5.0']
|
||||
|
||||
@@ -21,14 +21,17 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'asterisk_mbox'
|
||||
|
||||
SIGNAL_DISCOVER_PLATFORM = "asterisk_mbox.discover_platform"
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
SIGNAL_CDR_UPDATE = 'asterisk_mbox.message_updated'
|
||||
SIGNAL_CDR_REQUEST = 'asterisk_mbox.message_request'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
vol.Required(CONF_PORT): cv.port,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -41,9 +44,7 @@ def setup(hass, config):
|
||||
port = conf.get(CONF_PORT)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
|
||||
|
||||
discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password, config)
|
||||
|
||||
return True
|
||||
|
||||
@@ -51,31 +52,71 @@ def setup(hass, config):
|
||||
class AsteriskData:
|
||||
"""Store Asterisk mailbox data."""
|
||||
|
||||
def __init__(self, hass, host, port, password):
|
||||
def __init__(self, hass, host, port, password, config):
|
||||
"""Init the Asterisk data object."""
|
||||
from asterisk_mbox import Client as asteriskClient
|
||||
|
||||
self.hass = hass
|
||||
self.client = asteriskClient(host, port, password, self.handle_data)
|
||||
self.messages = []
|
||||
self.config = config
|
||||
self.messages = None
|
||||
self.cdr = None
|
||||
|
||||
async_dispatcher_connect(
|
||||
dispatcher_connect(
|
||||
self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages)
|
||||
dispatcher_connect(
|
||||
self.hass, SIGNAL_CDR_REQUEST, self._request_cdr)
|
||||
dispatcher_connect(
|
||||
self.hass, SIGNAL_DISCOVER_PLATFORM, self._discover_platform)
|
||||
# Only connect after signal connection to ensure we don't miss any
|
||||
self.client = asteriskClient(host, port, password, self.handle_data)
|
||||
|
||||
@callback
|
||||
def _discover_platform(self, component):
|
||||
_LOGGER.debug("Adding mailbox %s", component)
|
||||
self.hass.async_create_task(discovery.async_load_platform(
|
||||
self.hass, "mailbox", component, {}, self.config))
|
||||
|
||||
@callback
|
||||
def handle_data(self, command, msg):
|
||||
"""Handle changes to the mailbox."""
|
||||
from asterisk_mbox.commands import CMD_MESSAGE_LIST
|
||||
from asterisk_mbox.commands import (CMD_MESSAGE_LIST,
|
||||
CMD_MESSAGE_CDR_AVAILABLE,
|
||||
CMD_MESSAGE_CDR)
|
||||
|
||||
if command == CMD_MESSAGE_LIST:
|
||||
_LOGGER.debug("AsteriskVM sent updated message list")
|
||||
_LOGGER.debug("AsteriskVM sent updated message list: Len %d",
|
||||
len(msg))
|
||||
old_messages = self.messages
|
||||
self.messages = sorted(
|
||||
msg, key=lambda item: item['info']['origtime'], reverse=True)
|
||||
async_dispatcher_send(
|
||||
self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
|
||||
if not isinstance(old_messages, list):
|
||||
async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM,
|
||||
DOMAIN)
|
||||
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
|
||||
self.messages)
|
||||
elif command == CMD_MESSAGE_CDR:
|
||||
_LOGGER.debug("AsteriskVM sent updated CDR list: Len %d",
|
||||
len(msg.get('entries', [])))
|
||||
self.cdr = msg['entries']
|
||||
async_dispatcher_send(self.hass, SIGNAL_CDR_UPDATE, self.cdr)
|
||||
elif command == CMD_MESSAGE_CDR_AVAILABLE:
|
||||
if not isinstance(self.cdr, list):
|
||||
_LOGGER.debug("AsteriskVM adding CDR platform")
|
||||
self.cdr = []
|
||||
async_dispatcher_send(self.hass, SIGNAL_DISCOVER_PLATFORM,
|
||||
"asterisk_cdr")
|
||||
async_dispatcher_send(self.hass, SIGNAL_CDR_REQUEST)
|
||||
else:
|
||||
_LOGGER.debug("AsteriskVM sent unknown message '%d' len: %d",
|
||||
command, len(msg))
|
||||
|
||||
@callback
|
||||
def _request_messages(self):
|
||||
"""Handle changes to the mailbox."""
|
||||
_LOGGER.debug("Requesting message list")
|
||||
self.client.messages()
|
||||
|
||||
@callback
|
||||
def _request_cdr(self):
|
||||
"""Handle changes to the CDR."""
|
||||
_LOGGER.debug("Requesting CDR list")
|
||||
self.client.get_cdr()
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for August devices.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/august/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
@@ -124,6 +123,7 @@ def setup_august(hass, config, api, authenticator):
|
||||
|
||||
return True
|
||||
if state == AuthenticationState.BAD_PASSWORD:
|
||||
_LOGGER.error("Invalid password provided")
|
||||
return False
|
||||
if state == AuthenticationState.REQUIRES_VALIDATION:
|
||||
request_configuration(hass, config, api, authenticator)
|
||||
@@ -165,6 +165,7 @@ class AugustData:
|
||||
self._doorbell_detail_by_id = {}
|
||||
self._lock_status_by_id = {}
|
||||
self._lock_detail_by_id = {}
|
||||
self._door_state_by_id = {}
|
||||
self._activities_by_id = {}
|
||||
|
||||
@property
|
||||
@@ -184,6 +185,7 @@ class AugustData:
|
||||
|
||||
def get_device_activities(self, device_id, *activity_types):
|
||||
"""Return a list of activities."""
|
||||
_LOGGER.debug("Getting device activities")
|
||||
self._update_device_activities()
|
||||
|
||||
activities = self._activities_by_id.get(device_id, [])
|
||||
@@ -199,6 +201,7 @@ class AugustData:
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
|
||||
"""Update data object with latest from August API."""
|
||||
_LOGGER.debug("Updating device activities")
|
||||
for house_id in self.house_ids:
|
||||
activities = self._api.get_house_activities(self._access_token,
|
||||
house_id,
|
||||
@@ -218,14 +221,21 @@ class AugustData:
|
||||
def _update_doorbells(self):
|
||||
detail_by_id = {}
|
||||
|
||||
_LOGGER.debug("Start retrieving doorbell details")
|
||||
for doorbell in self._doorbells:
|
||||
_LOGGER.debug("Updating status for %s",
|
||||
doorbell.device_name)
|
||||
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
|
||||
self._access_token, doorbell.device_id)
|
||||
|
||||
_LOGGER.debug("Completed retrieving doorbell details")
|
||||
self._doorbell_detail_by_id = detail_by_id
|
||||
|
||||
def get_lock_status(self, lock_id):
|
||||
"""Return lock status."""
|
||||
"""Return status if the door is locked or unlocked.
|
||||
|
||||
This is status for the lock itself.
|
||||
"""
|
||||
self._update_locks()
|
||||
return self._lock_status_by_id.get(lock_id)
|
||||
|
||||
@@ -234,17 +244,43 @@ class AugustData:
|
||||
self._update_locks()
|
||||
return self._lock_detail_by_id.get(lock_id)
|
||||
|
||||
def get_door_state(self, lock_id):
|
||||
"""Return status if the door is open or closed.
|
||||
|
||||
This is the status from the door sensor.
|
||||
"""
|
||||
self._update_doors()
|
||||
return self._door_state_by_id.get(lock_id)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_doors(self):
|
||||
state_by_id = {}
|
||||
|
||||
_LOGGER.debug("Start retrieving door status")
|
||||
for lock in self._locks:
|
||||
_LOGGER.debug("Updating status for %s",
|
||||
lock.device_name)
|
||||
state_by_id[lock.device_id] = self._api.get_lock_door_status(
|
||||
self._access_token, lock.device_id)
|
||||
|
||||
_LOGGER.debug("Completed retrieving door status")
|
||||
self._door_state_by_id = state_by_id
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_locks(self):
|
||||
status_by_id = {}
|
||||
detail_by_id = {}
|
||||
|
||||
_LOGGER.debug("Start retrieving locks status")
|
||||
for lock in self._locks:
|
||||
_LOGGER.debug("Updating status for %s",
|
||||
lock.device_name)
|
||||
status_by_id[lock.device_id] = self._api.get_lock_status(
|
||||
self._access_token, lock.device_id)
|
||||
detail_by_id[lock.device_id] = self._api.get_lock_detail(
|
||||
self._access_token, lock.device_id)
|
||||
|
||||
_LOGGER.debug("Completed retrieving locks status")
|
||||
self._lock_status_by_id = status_by_id
|
||||
self._lock_detail_by_id = detail_by_id
|
||||
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "No hi ha serveis de notificaci\u00f3 disponibles."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Seleccioneu un dels serveis de notificaci\u00f3:",
|
||||
"title": "Configureu una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
|
||||
},
|
||||
"setup": {
|
||||
"description": "**notify.{notify_service}** ha enviat una contrasenya d'un sol \u00fas. Introdu\u00efu-la a continuaci\u00f3:",
|
||||
"title": "Verifiqueu la configuraci\u00f3"
|
||||
}
|
||||
},
|
||||
"title": "Contrasenya d'un sol \u00fas del servei de notificacions"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Codi no v\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
|
||||
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Keine Benachrichtigungsdienste verf\u00fcgbar."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Bitte w\u00e4hle 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:",
|
||||
"title": "\u00dcberpr\u00fcfe das Setup"
|
||||
}
|
||||
},
|
||||
"title": "Benachrichtig f\u00fcr One-Time Password"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn Sie diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
|
||||
"invalid_code": "Ung\u00fcltiger Code, bitte versuche es erneut. Wenn du diesen Fehler regelm\u00e4\u00dfig erhalten, stelle sicher, dass die Uhr deines Home Assistant-Systems korrekt ist."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "No notification services available."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Invalid code, please try again."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Please select one of the notification services:",
|
||||
"title": "Set up one-time password delivered by notify component"
|
||||
},
|
||||
"setup": {
|
||||
"description": "A one-time password has been sent via **notify.{notify_service}**. Please enter it below:",
|
||||
"title": "Verify setup"
|
||||
}
|
||||
},
|
||||
"title": "Notify One-Time Password"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Invalid code, please try again. If you get this error consistently, please make sure the clock of your Home Assistant system is accurate."
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Aucun service de notification disponible."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Code invalide. Veuillez essayer \u00e0 nouveau."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Veuillez s\u00e9lectionner l'un des services de notification:",
|
||||
"title": "Configurer un mot de passe \u00e0 usage unique d\u00e9livr\u00e9 par le composant notify"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :",
|
||||
"title": "V\u00e9rifier la configuration"
|
||||
}
|
||||
},
|
||||
"title": "Notifier un mot de passe unique"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Code invalide. Veuillez essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
|
||||
|
||||
35
homeassistant/components/auth/.translations/he.json
Normal file
35
homeassistant/components/auth/.translations/he.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "\u05d0\u05d9\u05df \u05e9\u05d9\u05e8\u05d5\u05ea\u05d9 notify \u05d6\u05de\u05d9\u05e0\u05d9\u05dd."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u05e7\u05d5\u05d3 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9, \u05d0\u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u05d1\u05d7\u05e8 \u05d0\u05ea \u05d0\u05d7\u05d3 \u05de\u05e9\u05e8\u05d5\u05ea\u05d9 notify",
|
||||
"title": "\u05d4\u05d2\u05d3\u05e8 \u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea \u05d4\u05e0\u05e9\u05dc\u05d7\u05ea \u05e2\u05dc \u05d9\u05d3\u05d9 \u05e8\u05db\u05d9\u05d1 notify"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea \u05e0\u05e9\u05dc\u05d7\u05d4 \u05e2\u05dc \u05d9\u05d3\u05d9 **{notify_service}**. \u05d4\u05d6\u05df \u05d0\u05d5\u05ea\u05d4 \u05dc\u05de\u05d8\u05d4:",
|
||||
"title": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d4\u05d4\u05ea\u05e7\u05e0\u05d4"
|
||||
}
|
||||
},
|
||||
"title": "\u05dc\u05d4\u05d5\u05d3\u05d9\u05e2 \u200b\u200b\u05e2\u05dc \u05e1\u05d9\u05e1\u05de\u05d4 \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05ea"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u05e7\u05d5\u05d3 \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9, \u05d0\u05e0\u05d0 \u05e0\u05e1\u05d4 \u05e9\u05d5\u05d1. \u05d0\u05dd \u05d0\u05ea\u05d4 \u05de\u05e7\u05d1\u05dc \u05d0\u05ea \u05d4\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d4\u05d6\u05d5 \u05d1\u05d0\u05d5\u05e4\u05df \u05e2\u05e7\u05d1\u05d9, \u05d5\u05d3\u05d0 \u05e9\u05d4\u05e9\u05e2\u05d5\u05df \u05e9\u05dc \u05de\u05e2\u05e8\u05db\u05ea \u05d4 - Home Assistant \u05e9\u05dc\u05da \u05de\u05d3\u05d5\u05d9\u05e7."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u05db\u05d3\u05d9 \u05dc\u05d4\u05e4\u05e2\u05d9\u05dc \u05d0\u05d9\u05de\u05d5\u05ea \u05d3\u05d5 \u05e9\u05dc\u05d1\u05d9 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea \u05e1\u05d9\u05e1\u05de\u05d0\u05d5\u05ea \u05d7\u05d3 \u05e4\u05e2\u05de\u05d9\u05d5\u05ea \u05de\u05d1\u05d5\u05e1\u05e1\u05d5\u05ea \u05d6\u05de\u05df, \u05e1\u05e8\u05d5\u05e7 \u05d0\u05ea \u05e7\u05d5\u05d3 QR \u05e2\u05dd \u05d9\u05d9\u05e9\u05d5\u05dd \u05d4\u05d0\u05d9\u05de\u05d5\u05ea \u05e9\u05dc\u05da. \u05d0\u05dd \u05d0\u05d9\u05df \u05dc\u05da \u05d7\u05e9\u05d1\u05d5\u05df \u05db\u05d6\u05d4, \u05d0\u05e0\u05d5 \u05de\u05de\u05dc\u05d9\u05e6\u05d9\u05dd \u05e2\u05dc [Google Authenticator] (https://support.google.com/accounts/answer/1066447) \u05d0\u05d5 [Authy] (https://authy.com/). \n\n {qr_code} \n \n \u05dc\u05d0\u05d7\u05e8 \u05e1\u05e8\u05d9\u05e7\u05ea \u05d4\u05e7\u05d5\u05d3, \u05d4\u05d6\u05df \u05d0\u05ea \u05d4\u05e7\u05d5\u05d3 \u05d1\u05df \u05e9\u05e9 \u05d4\u05e1\u05e4\u05e8\u05d5\u05ea \u05de\u05d4\u05d0\u05e4\u05dc\u05d9\u05e7\u05e6\u05d9\u05d4 \u05e9\u05dc\u05da \u05db\u05d3\u05d9 \u05dc\u05d0\u05de\u05ea \u05d0\u05ea \u05d4\u05d4\u05d2\u05d3\u05e8\u05d4. \u05d0\u05dd \u05d0\u05ea\u05d4 \u05e0\u05ea\u05e7\u05dc \u05d1\u05d1\u05e2\u05d9\u05d5\u05ea \u05d1\u05e1\u05e8\u05d9\u05e7\u05ea \u05e7\u05d5\u05d3 QR, \u05d1\u05e6\u05e2 \u05d4\u05d2\u05d3\u05e8\u05d4 \u05d9\u05d3\u05e0\u05d9\u05ea \u05e2\u05dd \u05e7\u05d5\u05d3 **`{code}`**.",
|
||||
"title": "\u05d4\u05d2\u05d3\u05e8 \u05d0\u05d9\u05de\u05d5\u05ea \u05d3\u05d5 \u05e9\u05dc\u05d1\u05d9 \u05d1\u05d0\u05de\u05e6\u05e2\u05d5\u05ea TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,21 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Nincs el\u00e9rhet\u0151 \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1s."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Be\u00e1ll\u00edt\u00e1s ellen\u0151rz\u00e9se"
|
||||
}
|
||||
}
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra. Ha ez a hiba folyamatosan el\u0151fordul, akkor gy\u0151z\u0151dj meg r\u00f3la, hogy a Home Assistant rendszered \u00f3r\u00e1ja pontosan j\u00e1r."
|
||||
|
||||
16
homeassistant/components/auth/.translations/id.json
Normal file
16
homeassistant/components/auth/.translations/id.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Kode salah, coba lagi. Jika Anda mendapatkan kesalahan ini secara konsisten, pastikan jam pada sistem Home Assistant anda akurat."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Untuk mengaktifkan otentikasi dua faktor menggunakan password satu kali berbasis waktu, pindai kode QR dengan aplikasi otentikasi Anda. Jika Anda tidak memilikinya, kami menyarankan [Google Authenticator] (https://support.google.com/accounts/answer/1066447) atau [Authy] (https://authy.com/). \n\n {qr_code} \n \n Setelah memindai kode, masukkan kode enam digit dari aplikasi Anda untuk memverifikasi pengaturan. Jika Anda mengalami masalah saat memindai kode QR, lakukan pengaturan manual dengan kode ** ` {code} ` **.",
|
||||
"title": "Siapkan otentikasi dua faktor menggunakan TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "\uc0ac\uc6a9 \uac00\ub2a5\ud55c \uc54c\ub9bc \uc11c\ube44\uc2a4\uac00 \uc5c6\uc2b5\ub2c8\ub2e4."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\uc54c\ub9bc \uc11c\ube44\uc2a4 \uc911 \ud558\ub098\ub97c \uc120\ud0dd\ud574\uc8fc\uc138\uc694:",
|
||||
"title": "\uc54c\ub9bc \uad6c\uc131\uc694\uc18c\uac00 \uc81c\uacf5\ud558\ub294 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc124\uc815"
|
||||
},
|
||||
"setup": {
|
||||
"description": "**notify.{notify_service}** \uc5d0\uc11c \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \ubcf4\ub0c8\uc2b5\ub2c8\ub2e4. \uc544\ub798\uc758 \uacf5\ub780\uc5d0 \uc785\ub825\ud574 \uc8fc\uc138\uc694:",
|
||||
"title": "\uc124\uc815 \ud655\uc778"
|
||||
}
|
||||
},
|
||||
"title": "\uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638 \uc54c\ub9bc"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\uc798\ubabb\ub41c \ucf54\ub4dc \uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694. \uc774 \uc624\ub958\uac00 \uc9c0\uc18d\uc801\uc73c\ub85c \ubc1c\uc0dd\ud55c\ub2e4\uba74 Home Assistant \uc758 \uc2dc\uac04\uc124\uc815\uc774 \uc62c\ubc14\ub978\uc9c0 \ud655\uc778\ud574\ubcf4\uc138\uc694."
|
||||
@@ -7,7 +26,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
|
||||
"title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2 \ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
|
||||
"title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
|
||||
}
|
||||
},
|
||||
"title": "TOTP (\uc2dc\uac04 \uae30\ubc18 OTP)"
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Keen Notifikatioun's D\u00e9ngscht disponibel."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ong\u00ebltege Code, prob\u00e9iert w.e.g. nach emol."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Wielt w.e.g. een Notifikatioun's D\u00e9ngscht aus:",
|
||||
"title": "Eemolegt Passwuert ariichte wat vun engem Notifikatioun's Komponente versch\u00e9ckt g\u00ebtt"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Een eemolegt Passwuert ass vun **notify.{notify_service}** gesch\u00e9ckt ginn. Gitt et w.e.g hei \u00ebnnen dr\u00ebnner an:",
|
||||
"title": "Astellungen iwwerpr\u00e9iwen"
|
||||
}
|
||||
},
|
||||
"title": "Eemolegt Passwuert Notifikatioun"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ong\u00ebltege Login, prob\u00e9iert w.e.g. nach emol. Falls d\u00ebse Feeler Message \u00ebmmer er\u00ebm optr\u00ebtt dann iwwerpr\u00e9ift op d'Z\u00e4it vum Home Assistant System richteg ass."
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Geen meldingsservices beschikbaar."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ongeldige code, probeer opnieuw."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Selecteer een van de meldingsdiensten:",
|
||||
"title": "Stel een \u00e9\u00e9nmalig wachtwoord in dat wordt afgegeven door een meldingscomponent"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Een \u00e9\u00e9nmalig wachtwoord is verzonden via **notify. {notify_service}**. Voer het hieronder in:",
|
||||
"title": "Controleer de instellingen"
|
||||
}
|
||||
},
|
||||
"title": "Eenmalig wachtwoord melden"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ongeldige code, probeer het opnieuw. Als u deze fout blijft krijgen, controleer dan of de klok van uw Home Assistant systeem correct is ingesteld."
|
||||
|
||||
16
homeassistant/components/auth/.translations/nn.json
Normal file
16
homeassistant/components/auth/.translations/nn.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Dersom du heile tida f\u00e5r denne feilen, m\u00e5 du s\u00f8rge for at klokka p\u00e5 Home Assistant-systemet ditt er n\u00f8yaktig."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "For \u00e5 aktivere tofaktorautentisering ved hjelp av tidsbaserte eingangspassord, skann QR-koden med autentiseringsappen din. Dersom du ikkje har ein, vil vi r\u00e5de deg til \u00e5 bruke anten [Google Authenticator] (https://support.google.com/accounts/answer/1066447) eller [Authy] (https://authy.com/). \n\n {qr_code} \n \nN\u00e5r du har skanna koda, skriv du inn den sekssifra koda fr\u00e5 appen din for \u00e5 stadfeste oppsettet. Dersom du har problemer med \u00e5 skanne QR-koda, gjer du eit manuelt oppsett med kode ** ` {code} ` **.",
|
||||
"title": "Konfigurer to-faktor-autentisering ved hjelp av TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Ingen varslingstjenester er tilgjengelig."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ugyldig kode, vennligst pr\u00f8v igjen."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Vennligst velg en av varslingstjenestene:",
|
||||
"title": "Sett opp engangspassord levert av varsel komponent"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Et engangspassord har blitt sendt via **notify.{notify_service}**. Vennligst skriv det inn nedenfor:",
|
||||
"title": "Bekreft oppsettet"
|
||||
}
|
||||
},
|
||||
"title": "Varsle engangspassord"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ugyldig kode, pr\u00f8v igjen. Hvis du f\u00e5r denne feilen konsekvent, m\u00e5 du s\u00f8rge for at klokken p\u00e5 Home Assistant systemet er riktig."
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Brak dost\u0119pnych us\u0142ug powiadamiania."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Prosz\u0119 wybra\u0107 jedn\u0105 z us\u0142ugi powiadamiania:",
|
||||
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wpisz je poni\u017cej:",
|
||||
"title": "Sprawd\u017a konfiguracj\u0119"
|
||||
}
|
||||
},
|
||||
"title": "Powiadomienie z has\u0142em jednorazowym"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Nieprawid\u0142owy kod, spr\u00f3buj ponownie. Je\u015bli b\u0142\u0105d b\u0119dzie si\u0119 powtarza\u0142, upewnij si\u0119, \u017ce czas zegara systemu Home Assistant jest prawid\u0142owy."
|
||||
|
||||
15
homeassistant/components/auth/.translations/pt-BR.json
Normal file
15
homeassistant/components/auth/.translations/pt-BR.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente. Se voc\u00ea obtiver este erro de forma consistente, certifique-se de que o rel\u00f3gio do sistema Home Assistant esteja correto."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configure a autentica\u00e7\u00e3o de dois fatores usando o TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Nenhum servi\u00e7o de notifica\u00e7\u00e3o dispon\u00edvel."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Por favor, selecione um dos servi\u00e7os de notifica\u00e7\u00e3o:",
|
||||
"title": "Configurar uma palavra passe entregue pela componente de notifica\u00e7\u00e3o"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Foi enviada uma palavra passe atrav\u00e9s de **notify.{notify_service}**. Por favor, insira-a:",
|
||||
"title": "Verificar a configura\u00e7\u00e3o"
|
||||
}
|
||||
},
|
||||
"title": "Notificar palavra passe de uso \u00fanico"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso."
|
||||
|
||||
34
homeassistant/components/auth/.translations/ro.json
Normal file
34
homeassistant/components/auth/.translations/ro.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Nu sunt disponibile servicii de notificare."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Cod invalid, va rugam incercati din nou."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Selecta\u021bi unul dintre serviciile de notificare:",
|
||||
"title": "Configura\u021bi o parol\u0103 unic\u0103 livrat\u0103 de o component\u0103 de notificare"
|
||||
},
|
||||
"setup": {
|
||||
"description": "O parol\u0103 unic\u0103 a fost trimis\u0103 prin **notify.{notify_service}**. Introduce\u021bi parola mai jos:",
|
||||
"title": "Verifica\u021bi configurarea"
|
||||
}
|
||||
},
|
||||
"title": "Notifica\u021bi o parol\u0103 unic\u0103"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Cod invalid, va rugam incercati din nou. Dac\u0103 primi\u021bi aceast\u0103 eroare \u00een mod consecvent, asigura\u021bi-v\u0103 c\u0103 ceasul sistemului dvs. Home Assistant este corect."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configura\u021bi autentificarea cu doi factori utiliz\u00e2nd TOTP"
|
||||
}
|
||||
},
|
||||
"title": "TOTP"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0441\u043b\u0443\u0436\u0431 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u0434, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u0434\u043d\u0443 \u0438\u0437 \u0441\u043b\u0443\u0436\u0431 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439:",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043e\u0441\u0442\u0430\u0432\u043a\u0438 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439 \u0447\u0435\u0440\u0435\u0437 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u041e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0447\u0435\u0440\u0435\u0437 **notify.{notify_service}**. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u0435\u0433\u043e \u043d\u0438\u0436\u0435:",
|
||||
"title": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443"
|
||||
}
|
||||
},
|
||||
"title": "\u0414\u043e\u0441\u0442\u0430\u0432\u043a\u0430 \u043e\u0434\u043d\u043e\u0440\u0430\u0437\u043e\u0432\u044b\u0445 \u043f\u0430\u0440\u043e\u043b\u0435\u0439"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u0415\u0441\u043b\u0438 \u0432\u044b \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0435 \u044d\u0442\u0443 \u043e\u0448\u0438\u0431\u043a\u0443, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0447\u0430\u0441\u044b \u0432 \u0432\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 Home Assistant \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f."
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Ni na voljo storitev obve\u0161\u010danja."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Neveljavna koda, poskusite znova."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Prosimo, izberite eno od storitev obve\u0161\u010danja:",
|
||||
"title": "Nastavite enkratno geslo, ki ga dostavite z obvestilno komponento"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Enkratno geslo je poslal **notify.{notify_service} **. Vnesite ga spodaj:",
|
||||
"title": "Preverite nastavitev"
|
||||
}
|
||||
},
|
||||
"title": "Obvesti Enkratno Geslo"
|
||||
},
|
||||
"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."
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Inga tillg\u00e4ngliga meddelande tj\u00e4nster."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Var god v\u00e4lj en av notifieringstj\u00e4nsterna:",
|
||||
"title": "Konfigurera ett eng\u00e5ngsl\u00f6senord levererat genom notifieringskomponenten"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:",
|
||||
"title": "Verifiera inst\u00e4llningen"
|
||||
}
|
||||
},
|
||||
"title": "Meddela eng\u00e5ngsl\u00f6senord"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ogiltig kod, f\u00f6rs\u00f6k igen. Om du flera g\u00e5nger i rad f\u00e5r detta fel, se till att klockan i din Home Assistant \u00e4r korrekt inst\u00e4lld."
|
||||
|
||||
9
homeassistant/components/auth/.translations/uk.json
Normal file
9
homeassistant/components/auth/.translations/uk.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"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."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "\u6ca1\u6709\u53ef\u7528\u7684\u901a\u77e5\u670d\u52a1\u3002"
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u4ee3\u7801\u65e0\u6548\uff0c\u8bf7\u518d\u8bd5\u4e00\u6b21\u3002"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u8bf7\u4ece\u4e0b\u9762\u9009\u62e9\u4e00\u4e2a\u901a\u77e5\u670d\u52a1\uff1a",
|
||||
"title": "\u8bbe\u7f6e\u7531\u901a\u77e5\u7ec4\u4ef6\u4f20\u9012\u7684\u4e00\u6b21\u6027\u5bc6\u7801"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u4e00\u6b21\u6027\u5bc6\u7801\u5df2\u7531 **notify.{notify_service}** \u53d1\u9001\u3002\u8bf7\u5728\u4e0b\u9762\u8f93\u5165\uff1a",
|
||||
"title": "\u9a8c\u8bc1\u8bbe\u7f6e"
|
||||
}
|
||||
},
|
||||
"title": "\u4e00\u6b21\u6027\u5bc6\u7801\u901a\u77e5"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u53e3\u4ee4\u65e0\u6548\uff0c\u8bf7\u91cd\u65b0\u8f93\u5165\u3002\u5982\u679c\u9519\u8bef\u53cd\u590d\u51fa\u73b0\uff0c\u8bf7\u786e\u4fdd Home Assistant \u7cfb\u7edf\u7684\u65f6\u95f4\u51c6\u786e\u65e0\u8bef\u3002"
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "\u6c92\u6709\u53ef\u7528\u7684\u901a\u77e5\u670d\u52d9\u3002"
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u9a57\u8b49\u78bc\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u8acb\u9078\u64c7\u4e00\u9805\u901a\u77e5\u670d\u52d9\uff1a",
|
||||
"title": "\u8a2d\u5b9a\u4e00\u6b21\u6027\u5bc6\u78bc\u50b3\u9001\u7d44\u4ef6"
|
||||
},
|
||||
"setup": {
|
||||
"description": "\u4e00\u6b21\u6027\u5bc6\u78bc\u5df2\u900f\u904e **notify.{notify_service}** \u50b3\u9001\u3002\u8acb\u65bc\u4e0b\u65b9\u8f38\u5165\uff1a",
|
||||
"title": "\u9a57\u8b49\u8a2d\u5b9a"
|
||||
}
|
||||
},
|
||||
"title": "\u901a\u77e5\u4e00\u6b21\u6027\u5bc6\u78bc"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u9a57\u8b49\u78bc\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002\u5047\u5982\u932f\u8aa4\u6301\u7e8c\u767c\u751f\uff0c\u8acb\u5148\u78ba\u5b9a\u60a8\u7684 Home Assistant \u7cfb\u7d71\u4e0a\u7684\u6642\u9593\u8a2d\u5b9a\u6b63\u78ba\u5f8c\uff0c\u518d\u8a66\u4e00\u6b21\u3002"
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u6383\u63cf\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u6383\u63cf\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
|
||||
"description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
|
||||
"title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -105,7 +105,6 @@ Home Assistant. User need to record the token in secure place.
|
||||
"id": 11,
|
||||
"type": "auth/long_lived_access_token",
|
||||
"client_name": "GPS Logger",
|
||||
"client_icon": null,
|
||||
"lifespan": 365
|
||||
}
|
||||
|
||||
@@ -433,7 +432,7 @@ def websocket_current_user(
|
||||
"""Get current user."""
|
||||
enabled_modules = await hass.auth.async_get_enabled_mfa(user)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], {
|
||||
'id': user.id,
|
||||
'name': user.name,
|
||||
@@ -468,7 +467,7 @@ def websocket_create_long_lived_access_token(
|
||||
access_token = hass.auth.async_create_access_token(
|
||||
refresh_token)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], access_token))
|
||||
|
||||
hass.async_create_task(
|
||||
@@ -480,8 +479,8 @@ def websocket_create_long_lived_access_token(
|
||||
def websocket_refresh_tokens(
|
||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
|
||||
"""Return metadata of users refresh tokens."""
|
||||
current_id = connection.request.get('refresh_token_id')
|
||||
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{
|
||||
current_id = connection.refresh_token_id
|
||||
connection.send_message(websocket_api.result_message(msg['id'], [{
|
||||
'id': refresh.id,
|
||||
'client_id': refresh.client_id,
|
||||
'client_name': refresh.client_name,
|
||||
@@ -509,7 +508,7 @@ def websocket_delete_refresh_token(
|
||||
|
||||
await hass.auth.async_remove_refresh_token(refresh_token)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], {}))
|
||||
|
||||
hass.async_create_task(
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
"""Helpers to resolve client ID/secret."""
|
||||
import asyncio
|
||||
from ipaddress import ip_address
|
||||
from html.parser import HTMLParser
|
||||
from ipaddress import ip_address, ip_network
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
|
||||
# IP addresses of loopback interfaces
|
||||
ALLOWED_IPS = (
|
||||
ip_address('127.0.0.1'),
|
||||
ip_address('::1'),
|
||||
)
|
||||
|
||||
# RFC1918 - Address allocation for Private Internets
|
||||
ALLOWED_NETWORKS = (
|
||||
ip_network('10.0.0.0/8'),
|
||||
ip_network('172.16.0.0/12'),
|
||||
ip_network('192.168.0.0/16'),
|
||||
)
|
||||
from homeassistant.util.network import is_local
|
||||
|
||||
|
||||
async def verify_redirect_uri(hass, client_id, redirect_uri):
|
||||
@@ -185,9 +174,7 @@ def _parse_client_id(client_id):
|
||||
# Not an ip address
|
||||
pass
|
||||
|
||||
if (address is None or
|
||||
address in ALLOWED_IPS or
|
||||
any(address in network for network in ALLOWED_NETWORKS)):
|
||||
if address is None or is_local(address):
|
||||
return parts
|
||||
|
||||
raise ValueError('Hostname should be a domain name or local IP address')
|
||||
|
||||
@@ -226,8 +226,9 @@ class LoginFlowResourceView(HomeAssistantView):
|
||||
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
|
||||
# @log_invalid_auth does not work here since it returns HTTP 200
|
||||
# need manually log failed login attempts
|
||||
if result['errors'] is not None and \
|
||||
result['errors'].get('base') == 'invalid_auth':
|
||||
if (result.get('errors') is not None and
|
||||
result['errors'].get('base') in ['invalid_auth',
|
||||
'invalid_code']):
|
||||
await process_wrong_login(request)
|
||||
return self.json(_prepare_result_json(result))
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ def websocket_setup_mfa(
|
||||
if flow_id is not None:
|
||||
result = await flow_manager.async_configure(
|
||||
flow_id, msg.get('user_input'))
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], _prepare_result_json(result)))
|
||||
return
|
||||
@@ -72,7 +72,7 @@ def websocket_setup_mfa(
|
||||
mfa_module_id = msg.get('mfa_module_id')
|
||||
mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id)
|
||||
if mfa_module is None:
|
||||
connection.send_message_outside(websocket_api.error_message(
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'no_module',
|
||||
'MFA module {} is not found'.format(mfa_module_id)))
|
||||
return
|
||||
@@ -80,7 +80,7 @@ def websocket_setup_mfa(
|
||||
result = await flow_manager.async_init(
|
||||
mfa_module_id, data={'user_id': connection.user.id})
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], _prepare_result_json(result)))
|
||||
|
||||
@@ -99,13 +99,13 @@ def websocket_depose_mfa(
|
||||
await hass.auth.async_disable_user_mfa(
|
||||
connection.user, msg['mfa_module_id'])
|
||||
except ValueError as err:
|
||||
connection.send_message_outside(websocket_api.error_message(
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'disable_failed',
|
||||
'Cannot disable MFA Module {}: {}'.format(
|
||||
mfa_module_id, err)))
|
||||
return
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], 'done'))
|
||||
|
||||
|
||||
@@ -11,6 +11,25 @@
|
||||
"error": {
|
||||
"invalid_code": "Invalid code, please try again. If you get this error consistently, please make sure the clock of your Home Assistant system is accurate."
|
||||
}
|
||||
},
|
||||
"notify": {
|
||||
"title": "Notify One-Time Password",
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Set up one-time password delivered by notify component",
|
||||
"description": "Please select one of the notification services:"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Verify setup",
|
||||
"description": "A one-time password has been sent via **notify.{notify_service}**. Please enter it below:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"no_available_service": "No notification services available."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Invalid code, please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import extract_domain_configs, script, condition
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
@@ -115,49 +114,6 @@ def is_on(hass, entity_id):
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_on(hass, entity_id=None):
|
||||
"""Turn on specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_off(hass, entity_id=None):
|
||||
"""Turn off specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def toggle(hass, entity_id=None):
|
||||
"""Toggle specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def trigger(hass, entity_id=None):
|
||||
"""Trigger specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def reload(hass):
|
||||
"""Reload the automation from config."""
|
||||
hass.services.call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def async_reload(hass):
|
||||
"""Reload the automation from config.
|
||||
|
||||
Returns a coroutine object.
|
||||
"""
|
||||
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the automation."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
||||
@@ -412,8 +368,8 @@ def _async_get_action(hass, config, name):
|
||||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.async_log_entry(
|
||||
hass, name, 'has been triggered', DOMAIN, entity_id)
|
||||
hass.components.logbook.async_log_entry(
|
||||
name, 'has been triggered', DOMAIN, entity_id)
|
||||
await script_obj.async_run(variables, context)
|
||||
|
||||
return action
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer event listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#event-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event_type = config.get(CONF_EVENT_TYPE)
|
||||
event_data_schema = vol.Schema(
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer Home Assistant core automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#homeassistant-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -23,8 +22,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event = config.get(CONF_EVENT)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Trigger an automation when a LiteJet switch is released.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/automation.litejet/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -33,8 +32,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
number = config.get(CONF_NUMBER)
|
||||
held_more_than = config.get(CONF_HELD_MORE_THAN)
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer MQTT listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
topic = config.get(CONF_TOPIC)
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
@@ -51,6 +49,6 @@ def async_trigger(hass, config, action):
|
||||
'trigger': data
|
||||
})
|
||||
|
||||
remove = yield from mqtt.async_subscribe(
|
||||
remove = await mqtt.async_subscribe(
|
||||
hass, topic, mqtt_automation_listener)
|
||||
return remove
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer numeric state listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
below = config.get(CONF_BELOW)
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer state listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#state-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
@@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
}), cv.key_dependency(CONF_FOR, CONF_TO))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer sun based automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event = config.get(CONF_EVENT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
|
||||
@@ -4,7 +4,6 @@ Offer template automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#template-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -23,8 +22,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
|
||||
@@ -4,7 +4,6 @@ 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 asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AT in config:
|
||||
at_time = config.get(CONF_AT)
|
||||
|
||||
53
homeassistant/components/automation/webhook.py
Normal file
53
homeassistant/components/automation/webhook.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Offer webhook triggered automation rules.
|
||||
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#webhook-trigger
|
||||
"""
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from aiohttp import hdrs
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ('webhook',)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'webhook',
|
||||
vol.Required(CONF_WEBHOOK_ID): cv.string,
|
||||
})
|
||||
|
||||
|
||||
async def _handle_webhook(action, hass, webhook_id, request):
|
||||
"""Handle incoming webhook."""
|
||||
result = {
|
||||
'platform': 'webhook',
|
||||
'webhook_id': webhook_id,
|
||||
}
|
||||
|
||||
if 'json' in request.headers.get(hdrs.CONTENT_TYPE, ''):
|
||||
result['json'] = await request.json()
|
||||
else:
|
||||
result['data'] = await request.post()
|
||||
|
||||
hass.async_run_job(action, {'trigger': result})
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Trigger based on incoming webhooks."""
|
||||
webhook_id = config.get(CONF_WEBHOOK_ID)
|
||||
hass.components.webhook.async_register(
|
||||
webhook_id, partial(_handle_webhook, action))
|
||||
|
||||
@callback
|
||||
def unregister():
|
||||
"""Unregister webhook."""
|
||||
hass.components.webhook.async_unregister(webhook_id)
|
||||
|
||||
return unregister
|
||||
@@ -4,7 +4,6 @@ Offer zone automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
@@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
zone_entity_id = config.get(CONF_ZONE)
|
||||
|
||||
@@ -38,6 +38,7 @@ AXIS_INCLUDE = EVENT_TYPES + PLATFORMS
|
||||
AXIS_DEFAULT_HOST = '192.168.0.90'
|
||||
AXIS_DEFAULT_USERNAME = 'root'
|
||||
AXIS_DEFAULT_PASSWORD = 'pass'
|
||||
DEFAULT_PORT = 80
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_INCLUDE):
|
||||
@@ -47,7 +48,7 @@ DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int,
|
||||
vol.Optional(CONF_PORT, default=80): cv.positive_int,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(ATTR_LOCATION, default=''): cv.string,
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for ADS binary sensors.
|
||||
For more details about this platform, please refer to the documentation.
|
||||
https://home-assistant.io/components/binary_sensor.ads/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -50,8 +49,7 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
self._ads_hub = ads_hub
|
||||
self.ads_var = ads_var
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register device notification."""
|
||||
def update(name, value):
|
||||
"""Handle device notifications."""
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for AlarmDecoder zone states- represented as binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.alarmdecoder/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@@ -64,8 +63,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
|
||||
self._relay_addr = relay_addr
|
||||
self._relay_chan = relay_chan
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_ZONE_FAULT, self._fault_callback)
|
||||
|
||||
@@ -4,8 +4,6 @@ Support for IP Webcam binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.android_ip_webcam/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.android_ip_webcam import (
|
||||
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME)
|
||||
@@ -13,9 +11,8 @@ from homeassistant.components.android_ip_webcam import (
|
||||
DEPENDENCIES = ['android_ip_webcam']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the IP Webcam binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
@@ -51,8 +48,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
state, _ = self._ipcam.export_sensor(self._sensor)
|
||||
self._state = state == 1.0
|
||||
|
||||
@@ -4,16 +4,26 @@ Support for August binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.august/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.components.august import DATA_AUGUST
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['august']
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
def _retrieve_door_state(data, lock):
|
||||
"""Get the latest state of the DoorSense sensor."""
|
||||
from august.lock import LockDoorStatus
|
||||
doorstate = data.get_door_state(lock.device_id)
|
||||
return doorstate == LockDoorStatus.OPEN
|
||||
|
||||
|
||||
def _retrieve_online_state(data, doorbell):
|
||||
"""Get the latest state of the sensor."""
|
||||
detail = data.get_doorbell_detail(doorbell.device_id)
|
||||
@@ -46,7 +56,11 @@ def _activity_time_based_state(data, doorbell, activity_types):
|
||||
|
||||
|
||||
# Sensor types: Name, device_class, state_provider
|
||||
SENSOR_TYPES = {
|
||||
SENSOR_TYPES_DOOR = {
|
||||
'door_open': ['Open', 'door', _retrieve_door_state],
|
||||
}
|
||||
|
||||
SENSOR_TYPES_DOORBELL = {
|
||||
'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state],
|
||||
'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state],
|
||||
'doorbell_online': ['Online', 'connectivity', _retrieve_online_state],
|
||||
@@ -58,14 +72,78 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
data = hass.data[DATA_AUGUST]
|
||||
devices = []
|
||||
|
||||
from august.lock import LockDoorStatus
|
||||
for door in data.locks:
|
||||
for sensor_type in SENSOR_TYPES_DOOR:
|
||||
state_provider = SENSOR_TYPES_DOOR[sensor_type][2]
|
||||
if state_provider(data, door) is LockDoorStatus.UNKNOWN:
|
||||
_LOGGER.debug(
|
||||
"Not adding sensor class %s for lock %s ",
|
||||
SENSOR_TYPES_DOOR[sensor_type][1], door.device_name
|
||||
)
|
||||
continue
|
||||
|
||||
_LOGGER.debug(
|
||||
"Adding sensor class %s for %s",
|
||||
SENSOR_TYPES_DOOR[sensor_type][1], door.device_name
|
||||
)
|
||||
devices.append(AugustDoorBinarySensor(data, sensor_type, door))
|
||||
|
||||
for doorbell in data.doorbells:
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
devices.append(AugustBinarySensor(data, sensor_type, doorbell))
|
||||
for sensor_type in SENSOR_TYPES_DOORBELL:
|
||||
_LOGGER.debug("Adding doorbell sensor class %s for %s",
|
||||
SENSOR_TYPES_DOORBELL[sensor_type][1],
|
||||
doorbell.device_name)
|
||||
devices.append(
|
||||
AugustDoorbellBinarySensor(data, sensor_type,
|
||||
doorbell)
|
||||
)
|
||||
|
||||
add_entities(devices, True)
|
||||
|
||||
|
||||
class AugustBinarySensor(BinarySensorDevice):
|
||||
class AugustDoorBinarySensor(BinarySensorDevice):
|
||||
"""Representation of an August Door binary sensor."""
|
||||
|
||||
def __init__(self, data, sensor_type, door):
|
||||
"""Initialize the sensor."""
|
||||
self._data = data
|
||||
self._sensor_type = sensor_type
|
||||
self._door = door
|
||||
self._state = None
|
||||
self._available = False
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return the availability of this sensor."""
|
||||
return self._available
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return SENSOR_TYPES_DOOR[self._sensor_type][1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return "{} {}".format(self._door.device_name,
|
||||
SENSOR_TYPES_DOOR[self._sensor_type][0])
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
state_provider = SENSOR_TYPES_DOOR[self._sensor_type][2]
|
||||
self._state = state_provider(self._data, self._door)
|
||||
|
||||
from august.lock import LockDoorStatus
|
||||
self._available = self._state != LockDoorStatus.UNKNOWN
|
||||
|
||||
|
||||
class AugustDoorbellBinarySensor(BinarySensorDevice):
|
||||
"""Representation of an August binary sensor."""
|
||||
|
||||
def __init__(self, data, sensor_type, doorbell):
|
||||
@@ -83,15 +161,15 @@ class AugustBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return SENSOR_TYPES[self._sensor_type][1]
|
||||
return SENSOR_TYPES_DOORBELL[self._sensor_type][1]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return "{} {}".format(self._doorbell.device_name,
|
||||
SENSOR_TYPES[self._sensor_type][0])
|
||||
SENSOR_TYPES_DOORBELL[self._sensor_type][0])
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
state_provider = SENSOR_TYPES[self._sensor_type][2]
|
||||
state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][2]
|
||||
self._state = state_provider(self._data, self._doorbell)
|
||||
|
||||
@@ -4,7 +4,6 @@ Use Bayesian Inference to trigger a binary sensor.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.bayesian/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
@@ -74,9 +73,8 @@ def update_probability(prior, prob_true, prob_false):
|
||||
return probability
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Bayesian Binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
observations = config.get(CONF_OBSERVATIONS)
|
||||
@@ -119,8 +117,7 @@ class BayesianBinarySensor(BinarySensorDevice):
|
||||
'state': self._process_state
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity about to be added."""
|
||||
@callback
|
||||
def async_threshold_sensor_state_listener(entity, old_state,
|
||||
@@ -214,7 +211,6 @@ class BayesianBinarySensor(BinarySensorDevice):
|
||||
ATTR_PROBABILITY_THRESHOLD: self._probability_threshold,
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Get the latest data and update the states."""
|
||||
self._deviation = bool(self.probability >= self._probability_threshold)
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
Support for Blink system camera control.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.blink/
|
||||
https://home-assistant.io/components/binary_sensor.blink.
|
||||
"""
|
||||
from homeassistant.components.blink import DOMAIN
|
||||
from homeassistant.components.blink import BLINK_DATA, BINARY_SENSORS
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
@@ -14,24 +15,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the blink binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = hass.data[BLINK_DATA]
|
||||
|
||||
data = hass.data[DOMAIN].blink
|
||||
devs = list()
|
||||
for name in data.cameras:
|
||||
devs.append(BlinkCameraMotionSensor(name, data))
|
||||
devs.append(BlinkSystemSensor(data))
|
||||
devs = []
|
||||
for camera in data.sync.cameras:
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
devs.append(BlinkBinarySensor(data, camera, sensor_type))
|
||||
add_entities(devs, True)
|
||||
|
||||
|
||||
class BlinkCameraMotionSensor(BinarySensorDevice):
|
||||
class BlinkBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Blink binary sensor."""
|
||||
|
||||
def __init__(self, name, data):
|
||||
def __init__(self, data, camera, sensor_type):
|
||||
"""Initialize the sensor."""
|
||||
self._name = 'blink_' + name + '_motion_enabled'
|
||||
self._camera_name = name
|
||||
self.data = data
|
||||
self._state = self.data.cameras[self._camera_name].armed
|
||||
self._type = sensor_type
|
||||
name, icon = BINARY_SENSORS[sensor_type]
|
||||
self._name = "{} {} {}".format(BLINK_DATA, camera, name)
|
||||
self._icon = icon
|
||||
self._camera = data.sync.cameras[camera]
|
||||
self._state = None
|
||||
self._unique_id = "{}-{}".format(self._camera.serial, self._type)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -46,29 +51,4 @@ class BlinkCameraMotionSensor(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Update sensor state."""
|
||||
self.data.refresh()
|
||||
self._state = self.data.cameras[self._camera_name].armed
|
||||
|
||||
|
||||
class BlinkSystemSensor(BinarySensorDevice):
|
||||
"""A representation of a Blink system sensor."""
|
||||
|
||||
def __init__(self, data):
|
||||
"""Initialize the sensor."""
|
||||
self._name = 'blink armed status'
|
||||
self.data = data
|
||||
self._state = self.data.arm
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the blink sensor."""
|
||||
return self._name.replace(" ", "_")
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the status of the sensor."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Update sensor state."""
|
||||
self.data.refresh()
|
||||
self._state = self.data.arm
|
||||
self._state = self._camera.attributes[self._type]
|
||||
|
||||
@@ -50,6 +50,12 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
self._sensor_name = sensor_name
|
||||
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
|
||||
self._state = None
|
||||
self._unique_id = '{}-{}'.format(self._device_id, self._sensor_name)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique ID."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
@@ -4,11 +4,11 @@ Reads vehicle status from BMW connected drive portal.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.bmw_connected_drive/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
|
||||
from homeassistant.const import LENGTH_KILOMETERS
|
||||
|
||||
DEPENDENCIES = ['bmw_connected_drive']
|
||||
|
||||
@@ -118,13 +118,17 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
result['lights_parking'] = vehicle_state.parking_lights.value
|
||||
elif self._attribute == 'condition_based_services':
|
||||
for report in vehicle_state.condition_based_services:
|
||||
result.update(self._format_cbs_report(report))
|
||||
result.update(
|
||||
self._format_cbs_report(report))
|
||||
elif self._attribute == 'check_control_messages':
|
||||
check_control_messages = vehicle_state.check_control_messages
|
||||
if not check_control_messages:
|
||||
result['check_control_messages'] = 'OK'
|
||||
else:
|
||||
result['check_control_messages'] = check_control_messages
|
||||
cbs_list = []
|
||||
for message in check_control_messages:
|
||||
cbs_list.append(message['ccmDescriptionShort'])
|
||||
result['check_control_messages'] = cbs_list
|
||||
elif self._attribute == 'charging_status':
|
||||
result['charging_status'] = vehicle_state.charging_status.value
|
||||
# pylint: disable=protected-access
|
||||
@@ -173,8 +177,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
self._state = (vehicle_state._attributes['connectionStatus'] ==
|
||||
'CONNECTED')
|
||||
|
||||
@staticmethod
|
||||
def _format_cbs_report(report):
|
||||
def _format_cbs_report(self, report):
|
||||
result = {}
|
||||
service_type = report.service_type.lower().replace('_', ' ')
|
||||
result['{} status'.format(service_type)] = report.state.value
|
||||
@@ -182,16 +185,17 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
result['{} date'.format(service_type)] = \
|
||||
report.due_date.strftime('%Y-%m-%d')
|
||||
if report.due_distance is not None:
|
||||
result['{} distance'.format(service_type)] = \
|
||||
'{} km'.format(report.due_distance)
|
||||
distance = round(self.hass.config.units.length(
|
||||
report.due_distance, LENGTH_KILOMETERS))
|
||||
result['{} distance'.format(service_type)] = '{} {}'.format(
|
||||
distance, self.hass.config.units.length_unit)
|
||||
return result
|
||||
|
||||
def update_callback(self):
|
||||
"""Schedule a state update."""
|
||||
self.schedule_update_ha_state(True)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Add callback after being added to hass.
|
||||
|
||||
Show latest data after startup.
|
||||
|
||||
@@ -127,6 +127,7 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
self._sensor.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._sensor.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
@@ -134,4 +135,5 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
'sw_version': self._sensor.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.egardia/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@@ -18,9 +17,8 @@ EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
|
||||
'IR': 'motion'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
|
||||
@@ -4,7 +4,6 @@ Support for Envisalink zone states- represented as binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.envisalink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
@@ -22,9 +21,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['envisalink']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Envisalink binary sensor devices."""
|
||||
configured_zones = discovery_info['zones']
|
||||
|
||||
@@ -56,8 +54,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
|
||||
_LOGGER.debug('Setting up zone: %s', zone_name)
|
||||
super().__init__(zone_name, info, controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)
|
||||
|
||||
@@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -46,13 +45,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the FFmpeg binary motion sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
if not await manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
entity = FFmpegMotion(hass, manager, config)
|
||||
@@ -98,8 +96,7 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||
self.ffmpeg = SensorMotion(
|
||||
manager.binary, hass.loop, self._async_callback)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_start_ffmpeg(self, entity_ids):
|
||||
async def _async_start_ffmpeg(self, entity_ids):
|
||||
"""Start a FFmpeg instance.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -116,7 +113,7 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||
)
|
||||
|
||||
# run
|
||||
yield from self.ffmpeg.open_sensor(
|
||||
await self.ffmpeg.open_sensor(
|
||||
input_source=self._config.get(CONF_INPUT),
|
||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -43,13 +42,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the FFmpeg noise binary sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
if not await manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
entity = FFmpegNoise(hass, manager, config)
|
||||
@@ -67,8 +65,7 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
self.ffmpeg = SensorNoise(
|
||||
manager.binary, hass.loop, self._async_callback)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_start_ffmpeg(self, entity_ids):
|
||||
async def _async_start_ffmpeg(self, entity_ids):
|
||||
"""Start a FFmpeg instance.
|
||||
|
||||
This method is a coroutine.
|
||||
@@ -82,7 +79,7 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
peak=self._config.get(CONF_PEAK),
|
||||
)
|
||||
|
||||
yield from self.ffmpeg.open_sensor(
|
||||
await self.ffmpeg.open_sensor(
|
||||
input_source=self._config.get(CONF_INPUT),
|
||||
output_dest=self._config.get(CONF_OUTPUT),
|
||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user