mirror of
https://github.com/home-assistant/core.git
synced 2026-01-11 18:17:17 +01:00
Compare commits
768 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca973b68e0 | ||
|
|
d19a8ec7da | ||
|
|
4152ac4aa2 | ||
|
|
efdc7042df | ||
|
|
ebf4be3711 | ||
|
|
16d72d2351 | ||
|
|
b2210f429e | ||
|
|
7410bc90f0 | ||
|
|
7e15f179c6 | ||
|
|
321eb2ec6f | ||
|
|
2ee73ca911 | ||
|
|
19a529e917 | ||
|
|
7f065e38a7 | ||
|
|
c4a4802a8c | ||
|
|
44e4f8d1ba | ||
|
|
eaf525d41d | ||
|
|
6d7dbe5536 | ||
|
|
ab397e2b1a | ||
|
|
6be81feb2d | ||
|
|
9b1a75a74b | ||
|
|
8792fd22b9 | ||
|
|
5dd0193ba6 | ||
|
|
b159e8acee | ||
|
|
a99c8eb6c6 | ||
|
|
4218b31e7b | ||
|
|
35bae1eef2 | ||
|
|
d119610cf1 | ||
|
|
4aed41cbe8 | ||
|
|
c462292e4d | ||
|
|
b04e7bba9f | ||
|
|
e6364b4ff6 | ||
|
|
36b9c0a946 | ||
|
|
9086119082 | ||
|
|
dc94079d74 | ||
|
|
78c27b99bd | ||
|
|
f054e9ee54 | ||
|
|
205e83a6d5 | ||
|
|
5063464d5e | ||
|
|
38af04c6ce | ||
|
|
03225cf20f | ||
|
|
3682080da2 | ||
|
|
13cb9cb07b | ||
|
|
05204a982e | ||
|
|
18b288dcfe | ||
|
|
60d7e32f81 | ||
|
|
6a5c7ef43f | ||
|
|
e5c4bba906 | ||
|
|
e07fb24987 | ||
|
|
e7b84432f9 | ||
|
|
b0e062b2f8 | ||
|
|
259121c7a7 | ||
|
|
326241d9d8 | ||
|
|
7c7da9df05 | ||
|
|
cf3f1c3081 | ||
|
|
f00d5cb8ca | ||
|
|
3920de7119 | ||
|
|
fd409a16a1 | ||
|
|
7145afe729 | ||
|
|
70760b5d3b | ||
|
|
d418355d4d | ||
|
|
81ba666db7 | ||
|
|
a147401034 | ||
|
|
36e9f523d1 | ||
|
|
ae257651bf | ||
|
|
53cc3262bd | ||
|
|
2e5b4946e1 | ||
|
|
67c49a7662 | ||
|
|
d06807c634 | ||
|
|
2321603eb7 | ||
|
|
ba20ffdde7 | ||
|
|
cf8907ed0f | ||
|
|
95176b0666 | ||
|
|
f0d9844dfb | ||
|
|
54f8f1223f | ||
|
|
339a839dbe | ||
|
|
e2e10b91a7 | ||
|
|
a9d242a213 | ||
|
|
49581a4a2a | ||
|
|
d8a11fd706 | ||
|
|
d63cf94d6d | ||
|
|
7d8a309017 | ||
|
|
99eeb01525 | ||
|
|
92b07ba8d1 | ||
|
|
c2b06b9e55 | ||
|
|
dd67192057 | ||
|
|
a5ffe0f72b | ||
|
|
7937064fb7 | ||
|
|
0762c7caef | ||
|
|
32b6fb60d8 | ||
|
|
4a85ab1ecb | ||
|
|
981f6fa027 | ||
|
|
125088449a | ||
|
|
fec7c87ff3 | ||
|
|
d333593aa6 | ||
|
|
4e03176634 | ||
|
|
e20e0425b1 | ||
|
|
228b030c82 | ||
|
|
7a979e9f72 | ||
|
|
25c4c9b63c | ||
|
|
03970764d8 | ||
|
|
168e1f0e2d | ||
|
|
d3386907a4 | ||
|
|
de3c76983a | ||
|
|
b9d8789771 | ||
|
|
b186b27600 | ||
|
|
3ca446dda1 | ||
|
|
1bc5042bf9 | ||
|
|
23c39ebefd | ||
|
|
ff83efe376 | ||
|
|
17ba813a6d | ||
|
|
78dd010a04 | ||
|
|
88021ba404 | ||
|
|
491b3d707c | ||
|
|
59141a4063 | ||
|
|
b6805853f1 | ||
|
|
7511286a25 | ||
|
|
70979d855d | ||
|
|
a1010cc63a | ||
|
|
3a2c3fe589 | ||
|
|
eeb9992fde | ||
|
|
191f24b2b9 | ||
|
|
761b4181c0 | ||
|
|
7d8ca2010b | ||
|
|
dbef8f0b78 | ||
|
|
ed85e368e3 | ||
|
|
4867ed23dc | ||
|
|
9f35d4dfca | ||
|
|
b434ffba2d | ||
|
|
a60712d826 | ||
|
|
53078f3069 | ||
|
|
3416d3f5f1 | ||
|
|
b1cc9bf452 | ||
|
|
6fe6dcfac9 | ||
|
|
001515bdc4 | ||
|
|
c1aaef28a9 | ||
|
|
f7e9215f5e | ||
|
|
e2a2fe36fc | ||
|
|
3628fcf083 | ||
|
|
44cad7df30 | ||
|
|
bbd58d7357 | ||
|
|
222748dfbf | ||
|
|
a0eca9c6d1 | ||
|
|
b556b86301 | ||
|
|
e82b358831 | ||
|
|
9658f4383c | ||
|
|
f6c504610b | ||
|
|
f5c633415d | ||
|
|
c5157c1027 | ||
|
|
bba1e2adc9 | ||
|
|
a63714dc86 | ||
|
|
ab74ac8eca | ||
|
|
f19b934869 | ||
|
|
efd155dd3c | ||
|
|
14d052d242 | ||
|
|
39cee987d9 | ||
|
|
6ed765698a | ||
|
|
138350fe3d | ||
|
|
88b7f429c8 | ||
|
|
992516ba86 | ||
|
|
484841c890 | ||
|
|
4e522448b1 | ||
|
|
8645f244da | ||
|
|
be64422d1c | ||
|
|
a0997bd214 | ||
|
|
802bc322e8 | ||
|
|
ed8eda86e2 | ||
|
|
bbc2f1d808 | ||
|
|
83203a10a7 | ||
|
|
113ea2d1dc | ||
|
|
d128f4e51f | ||
|
|
de3a9d552a | ||
|
|
948ef7523e | ||
|
|
9e8340432c | ||
|
|
0a067f4cc7 | ||
|
|
68f92d2e7c | ||
|
|
e14893416f | ||
|
|
9751fed493 | ||
|
|
dc8c331c68 | ||
|
|
d052d45712 | ||
|
|
4242411089 | ||
|
|
f396266c74 | ||
|
|
21d8ecdacd | ||
|
|
6bdb2fe5a0 | ||
|
|
c1c23bb4b6 | ||
|
|
98fef81d19 | ||
|
|
71cab65df6 | ||
|
|
111e515da7 | ||
|
|
f7fc4c6f15 | ||
|
|
a783006f92 | ||
|
|
5b8aeafdb9 | ||
|
|
c1a6131aa8 | ||
|
|
4821858afb | ||
|
|
446390a8d1 | ||
|
|
6a665ffb84 | ||
|
|
10570f5ad6 | ||
|
|
2c1083bda1 | ||
|
|
f8a0a0ba59 | ||
|
|
1d14a17ffd | ||
|
|
a8c9303892 | ||
|
|
bf41674e06 | ||
|
|
6e6ae173fd | ||
|
|
7d5c1581f1 | ||
|
|
6d5fb49687 | ||
|
|
e96ac74b11 | ||
|
|
27b1d448a3 | ||
|
|
84c156e8f5 | ||
|
|
5dcf92fa2a | ||
|
|
6c614df96e | ||
|
|
347ba1a2d8 | ||
|
|
8d0d676ff2 | ||
|
|
781b7687a4 | ||
|
|
286baed9ad | ||
|
|
43ad3ae2d4 | ||
|
|
19d34daef0 | ||
|
|
7c80ef714e | ||
|
|
9ca67c36cd | ||
|
|
6aa8916654 | ||
|
|
2261ce30e3 | ||
|
|
7f5ca314ec | ||
|
|
32166fd56a | ||
|
|
e8173fbc16 | ||
|
|
63552abce5 | ||
|
|
16cb7388ee | ||
|
|
eacfbc048a | ||
|
|
da832dbda2 | ||
|
|
f51a3738aa | ||
|
|
6d431c3fc3 | ||
|
|
2821820281 | ||
|
|
3713dfe139 | ||
|
|
c076b805e7 | ||
|
|
7a44eee093 | ||
|
|
485979cd86 | ||
|
|
5a80d4e5ea | ||
|
|
c3d322f26c | ||
|
|
230b73d14a | ||
|
|
042f292e4f | ||
|
|
daa00bc65a | ||
|
|
1b22f2d8b8 | ||
|
|
1e672b93e7 | ||
|
|
6ee3c1b3e5 | ||
|
|
156206dfee | ||
|
|
7dcb2ae24c | ||
|
|
6b03cb913c | ||
|
|
079724be05 | ||
|
|
f899ce8fbf | ||
|
|
ffd3889271 | ||
|
|
87c69452f9 | ||
|
|
1af65f8f23 | ||
|
|
f0a1beac5d | ||
|
|
4fdbbc497d | ||
|
|
de72eb8fe9 | ||
|
|
2a4971dec7 | ||
|
|
4d7fb2c7de | ||
|
|
184a54cc58 | ||
|
|
c6480e46c4 | ||
|
|
b228695907 | ||
|
|
51c06e35cf | ||
|
|
b8df2d4042 | ||
|
|
2d36d4d9f3 | ||
|
|
03d6071a45 | ||
|
|
6ce9be6b3a | ||
|
|
ed1a883b52 | ||
|
|
f9ee29a5cd | ||
|
|
8d0b7adf24 | ||
|
|
28fec209e0 | ||
|
|
5ad0baf128 | ||
|
|
49d410546a | ||
|
|
722926b315 | ||
|
|
c898fb1f16 | ||
|
|
4f96eeb06e | ||
|
|
316eb59de2 | ||
|
|
5d29d88888 | ||
|
|
210226daac | ||
|
|
f2a2727a15 | ||
|
|
1d8a5147e9 | ||
|
|
3077444d62 | ||
|
|
7829e61361 | ||
|
|
fb985e2909 | ||
|
|
39847ea651 | ||
|
|
17bdcac61b | ||
|
|
e37974c5fc | ||
|
|
46ce114066 | ||
|
|
d68a24b3b8 | ||
|
|
336b00765d | ||
|
|
bb29f16054 | ||
|
|
42ab4e1366 | ||
|
|
722b9ba49b | ||
|
|
f3748cc4fa | ||
|
|
eec3bad94f | ||
|
|
dc21c61a44 | ||
|
|
da9c0a1fd7 | ||
|
|
6f2ee9a34c | ||
|
|
a378e18a3f | ||
|
|
63fcf9d425 | ||
|
|
17b57099ae | ||
|
|
1143499301 | ||
|
|
72fa170265 | ||
|
|
60148f3e83 | ||
|
|
635d36c6ba | ||
|
|
2280dc2a34 | ||
|
|
0d0e0b8ba3 | ||
|
|
a8444b22e7 | ||
|
|
e8d8b75c07 | ||
|
|
02c05e2490 | ||
|
|
92aeef82ef | ||
|
|
909a06566e | ||
|
|
8840c227d2 | ||
|
|
6299c054c8 | ||
|
|
38da81c308 | ||
|
|
6ce9b35e81 | ||
|
|
bf67d0e650 | ||
|
|
fcf97524a2 | ||
|
|
b651cdd8f2 | ||
|
|
6838de3786 | ||
|
|
c9300a98e0 | ||
|
|
de7a4b9501 | ||
|
|
a046e2ed20 | ||
|
|
eaba3b315c | ||
|
|
4837254146 | ||
|
|
371fe9c78f | ||
|
|
22a007a785 | ||
|
|
66dcb6c947 | ||
|
|
fab991bbf6 | ||
|
|
3fd61d8f45 | ||
|
|
e4ef6b91d6 | ||
|
|
dd7bffc28c | ||
|
|
26340fd9df | ||
|
|
fe5626b927 | ||
|
|
b3a47722f0 | ||
|
|
2053c8a908 | ||
|
|
13d6e56106 | ||
|
|
1f041d54d9 | ||
|
|
8d48272cbd | ||
|
|
facd833e6d | ||
|
|
0e2d98dbf5 | ||
|
|
d43a8e593a | ||
|
|
c7c0df53aa | ||
|
|
612dd30201 | ||
|
|
f0d9e5d7ff | ||
|
|
d18709df5b | ||
|
|
f32911d036 | ||
|
|
ad8fe8a93a | ||
|
|
b4dbfe9bbd | ||
|
|
ae32d208d9 | ||
|
|
f5d1f53fab | ||
|
|
96bd153c80 | ||
|
|
7e2e82d956 | ||
|
|
5d4b1ecd3b | ||
|
|
c25c4c85d6 | ||
|
|
78c44180f4 | ||
|
|
416f64fc70 | ||
|
|
f25d56d666 | ||
|
|
6f043f3c5c | ||
|
|
6500cb7915 | ||
|
|
d85ed8d0fe | ||
|
|
c82ca62820 | ||
|
|
28964806c5 | ||
|
|
c414ecd4f0 | ||
|
|
72f100723f | ||
|
|
9e4da37022 | ||
|
|
e5f000f976 | ||
|
|
e2408cc804 | ||
|
|
9bfeb3b5af | ||
|
|
c5c409bed3 | ||
|
|
8bff813014 | ||
|
|
a4944da68f | ||
|
|
bc64053214 | ||
|
|
c7416c8986 | ||
|
|
429628ec1d | ||
|
|
16dafaa5af | ||
|
|
f0231c1f29 | ||
|
|
5995c2f313 | ||
|
|
80d2c76e85 | ||
|
|
d2cea84254 | ||
|
|
a4b88fc31b | ||
|
|
f5c2e7ff68 | ||
|
|
00ff305bd7 | ||
|
|
66d14da5e9 | ||
|
|
ba9fef4de6 | ||
|
|
0a558a0e82 | ||
|
|
2c202690d8 | ||
|
|
04bde68db3 | ||
|
|
2d77a2bb39 | ||
|
|
52f57b755e | ||
|
|
870728f68f | ||
|
|
48f40453f7 | ||
|
|
073126755c | ||
|
|
034eb9ae1a | ||
|
|
d34a4fb6e3 | ||
|
|
7a9ceb6f54 | ||
|
|
a06000c76d | ||
|
|
2a0bd8d330 | ||
|
|
ead158b68c | ||
|
|
bfd9a5a863 | ||
|
|
56b185f7ab | ||
|
|
34ccfae565 | ||
|
|
dc8a0205ee | ||
|
|
7471211b60 | ||
|
|
04b68902e3 | ||
|
|
ebe4418afe | ||
|
|
eaa2791539 | ||
|
|
7059b6c6c1 | ||
|
|
5d15b257c4 | ||
|
|
669929de06 | ||
|
|
eb7adc74ef | ||
|
|
3b3050434a | ||
|
|
7e9dcfa4c9 | ||
|
|
c193d80ec5 | ||
|
|
2e3524147c | ||
|
|
f28fa7447e | ||
|
|
069454323e | ||
|
|
ed1d6f1027 | ||
|
|
28ed304c93 | ||
|
|
3e150bb2b3 | ||
|
|
247edf1b69 | ||
|
|
219ed7331c | ||
|
|
47bfef9640 | ||
|
|
767d3c6206 | ||
|
|
e4a826d1c1 | ||
|
|
a71d5f4614 | ||
|
|
6c358fa6a3 | ||
|
|
26209de2f2 | ||
|
|
678f284015 | ||
|
|
64c5d26a84 | ||
|
|
2edebfee0a | ||
|
|
b1c0cabe6c | ||
|
|
17e5740a0c | ||
|
|
8b9eab196c | ||
|
|
65c6f72c9d | ||
|
|
fe1a85047e | ||
|
|
74010fc2df | ||
|
|
0e16f7f307 | ||
|
|
18aa1037dd | ||
|
|
aad26599ae | ||
|
|
a9412d27aa | ||
|
|
a9e2dd3427 | ||
|
|
f2296e1ff8 | ||
|
|
134445f622 | ||
|
|
cad9e9a4cb | ||
|
|
1db4df6d3a | ||
|
|
7c36c5d9b4 | ||
|
|
3333dcc6c2 | ||
|
|
681dc72a15 | ||
|
|
b0780110c7 | ||
|
|
b087ea101d | ||
|
|
129d720d8e | ||
|
|
6174c1754b | ||
|
|
4c11a3461f | ||
|
|
0b947882ac | ||
|
|
2014e42e4e | ||
|
|
2ae0c5653e | ||
|
|
e4874fd7c7 | ||
|
|
18d027a10d | ||
|
|
b08294386b | ||
|
|
acb521330c | ||
|
|
231b62d043 | ||
|
|
905bb36e6a | ||
|
|
25cbc8317f | ||
|
|
6265d1b747 | ||
|
|
702b1be985 | ||
|
|
15368d4ca1 | ||
|
|
5601fbdc7a | ||
|
|
8523933605 | ||
|
|
0300229085 | ||
|
|
d0ffb1bc52 | ||
|
|
2b9bb7963d | ||
|
|
945606238c | ||
|
|
aa9b5e6ea5 | ||
|
|
9d5dee574a | ||
|
|
ea35ffbc81 | ||
|
|
d05a1e35fc | ||
|
|
5ba02c531e | ||
|
|
7e246e4680 | ||
|
|
bd29cd2ba2 | ||
|
|
cee57aab24 | ||
|
|
a2916a9c47 | ||
|
|
49c7b422f2 | ||
|
|
a1d586c793 | ||
|
|
c7dad113d9 | ||
|
|
844337ca42 | ||
|
|
0fd17a7c35 | ||
|
|
6f74b672a3 | ||
|
|
f58e5f442d | ||
|
|
98b47cecbd | ||
|
|
e7a0759e1c | ||
|
|
bdaf9cfae2 | ||
|
|
4f0776de13 | ||
|
|
323fe87b57 | ||
|
|
c72460ccf0 | ||
|
|
49343c9b02 | ||
|
|
e35d4f0a2c | ||
|
|
86e89b7c26 | ||
|
|
44cfd2999c | ||
|
|
f5030d9ebf | ||
|
|
137933a774 | ||
|
|
905a994972 | ||
|
|
ec201f3458 | ||
|
|
cff4f8ec9a | ||
|
|
64cbfdfd77 | ||
|
|
8fe339d2a8 | ||
|
|
c209c10887 | ||
|
|
b33d89326f | ||
|
|
1aca6f922f | ||
|
|
9b0dbf3fbe | ||
|
|
4ac9e7edf4 | ||
|
|
c144a3339f | ||
|
|
acc767cdb1 | ||
|
|
880f18a37e | ||
|
|
f7c9787418 | ||
|
|
c204a7c787 | ||
|
|
2cbab48e1b | ||
|
|
86daec8c59 | ||
|
|
0f879a6c60 | ||
|
|
a3e36e6c66 | ||
|
|
730e0a0094 | ||
|
|
13ec8b143d | ||
|
|
ed2d54ab45 | ||
|
|
72c35468b3 | ||
|
|
87c0fd98c7 | ||
|
|
1d2e930900 | ||
|
|
ad24cbddcc | ||
|
|
65f22b09ae | ||
|
|
569f7e2fea | ||
|
|
12dc0db856 | ||
|
|
6d5a87afb6 | ||
|
|
30ad591a59 | ||
|
|
be37bb14b7 | ||
|
|
53a99dc9fa | ||
|
|
2f07ffc4e4 | ||
|
|
8991690d53 | ||
|
|
764343dbf8 | ||
|
|
e11e066684 | ||
|
|
40af9f2676 | ||
|
|
424fe95ce4 | ||
|
|
81a6178931 | ||
|
|
e9508405bc | ||
|
|
6ae3fa40cf | ||
|
|
434d2afbfc | ||
|
|
0376cc0917 | ||
|
|
4cb1f93019 | ||
|
|
ebfb380449 | ||
|
|
3e41422caa | ||
|
|
cab6c694c5 | ||
|
|
37034a7450 | ||
|
|
990fbdf3ca | ||
|
|
dfd2d631ae | ||
|
|
12182d6e49 | ||
|
|
d7017f2138 | ||
|
|
ec1c395f09 | ||
|
|
71cb4df817 | ||
|
|
e51427b284 | ||
|
|
8e441ba03b | ||
|
|
5b1c51bdf6 | ||
|
|
8624799c45 | ||
|
|
1aaf49d0c1 | ||
|
|
10263230f7 | ||
|
|
6f84fa4ce5 | ||
|
|
a1c0544e35 | ||
|
|
a59d26b1fa | ||
|
|
170a0c9888 | ||
|
|
e084a260c6 | ||
|
|
8709a397f6 | ||
|
|
9fed3acc90 | ||
|
|
0713dbc7a3 | ||
|
|
5609b42863 | ||
|
|
24c6285567 | ||
|
|
5bdbf3dfc0 | ||
|
|
6d59dad1ce | ||
|
|
99c6a10b99 | ||
|
|
7ad870c4ff | ||
|
|
89e0b26b73 | ||
|
|
8dcfd35b8b | ||
|
|
38fd9b65bf | ||
|
|
105522f03f | ||
|
|
8b9dc71cde | ||
|
|
ff0fd71608 | ||
|
|
12a53e2747 | ||
|
|
384f63dd1d | ||
|
|
78a3c01f27 | ||
|
|
5426e5c875 | ||
|
|
766875f702 | ||
|
|
7d6ef4445e | ||
|
|
84711aad90 | ||
|
|
b3bf6c4be2 | ||
|
|
c7efe5b7dd | ||
|
|
96f9a12541 | ||
|
|
336bdb1889 | ||
|
|
62d4f23833 | ||
|
|
3c869c6ed6 | ||
|
|
a3fc2c7fee | ||
|
|
2d3034be11 | ||
|
|
1419005082 | ||
|
|
f43234b533 | ||
|
|
f08fd8182c | ||
|
|
0c008663ad | ||
|
|
63ae275182 | ||
|
|
d8fde94763 | ||
|
|
55ee8959ba | ||
|
|
94316f07a2 | ||
|
|
e750428e9d | ||
|
|
3af7c67bf1 | ||
|
|
f1fc3c762a | ||
|
|
b4d682ca75 | ||
|
|
cad0bde95b | ||
|
|
74b0740e1c | ||
|
|
5bde72d490 | ||
|
|
af69a307a8 | ||
|
|
7bbef68b2a | ||
|
|
abde8c40c9 | ||
|
|
07b2f38046 | ||
|
|
280c1601a2 | ||
|
|
df24ecf395 | ||
|
|
af5d0b3443 | ||
|
|
2b68bec428 | ||
|
|
ffcc41d6ef | ||
|
|
2d8ef36a6c | ||
|
|
5af7666a61 | ||
|
|
bfe259f7a0 | ||
|
|
68d2851ecf | ||
|
|
8332d4e359 | ||
|
|
390b727869 | ||
|
|
deb10a1c4d | ||
|
|
3d8e425113 | ||
|
|
a8ee11e732 | ||
|
|
748fff7ebc | ||
|
|
9a71717047 | ||
|
|
920f9f132b | ||
|
|
3aa3130d05 | ||
|
|
caa16da5c5 | ||
|
|
94e270f828 | ||
|
|
502ebd2a31 | ||
|
|
0db9c04f21 | ||
|
|
72bb0e97e0 | ||
|
|
5123487705 | ||
|
|
3d9ff372fc | ||
|
|
9123dfce6d | ||
|
|
27edbe5c0f | ||
|
|
ffe832763d | ||
|
|
becd94fe2f | ||
|
|
a0a001db71 | ||
|
|
d65ac7421d | ||
|
|
b43b542667 | ||
|
|
676c95ed2d | ||
|
|
5b0a475197 | ||
|
|
4521d59bec | ||
|
|
8f083e17f8 | ||
|
|
9a3895c79e | ||
|
|
eea7e2fa2d | ||
|
|
990e076c2c | ||
|
|
73fa76d792 | ||
|
|
6df1fae447 | ||
|
|
3417c6ad8d | ||
|
|
95592d9283 | ||
|
|
09e3bf94eb | ||
|
|
bc13c9db83 | ||
|
|
183e0543b4 | ||
|
|
d478517c51 | ||
|
|
b224fd324d | ||
|
|
8c627e2b8b | ||
|
|
c8d26d99f0 | ||
|
|
0f26ebe954 | ||
|
|
5513ffc33c | ||
|
|
6a6ea263cf | ||
|
|
1a789a05db | ||
|
|
47e31dc9ee | ||
|
|
0100f87ff2 | ||
|
|
8c78a210ef | ||
|
|
dd81af4cd5 | ||
|
|
2cfbd0dc1d | ||
|
|
a470cc212e | ||
|
|
f7b129d790 | ||
|
|
e02d5e7ff1 | ||
|
|
323992e224 | ||
|
|
f744467c5d | ||
|
|
7cbe017932 | ||
|
|
c1b0ab75e1 | ||
|
|
51c41ba4e3 | ||
|
|
51dd9b6dde | ||
|
|
d697e8e677 | ||
|
|
d5df1c070d | ||
|
|
273db75248 | ||
|
|
5ef7a8d55a | ||
|
|
2ca4bde06a | ||
|
|
4ceb13291f | ||
|
|
03a5d4e131 | ||
|
|
5de828d6e2 | ||
|
|
b84e551aea | ||
|
|
c48ef281ab | ||
|
|
b10fd172fd | ||
|
|
314582ef0c | ||
|
|
b1fd9daf5f | ||
|
|
7fe2dafa04 | ||
|
|
48619c9d7c | ||
|
|
0e1cc05189 | ||
|
|
ce9673b06d | ||
|
|
a9634199e6 | ||
|
|
cf6f916ed4 | ||
|
|
8ca45acb4e | ||
|
|
23b2ca50e2 | ||
|
|
526405c83b | ||
|
|
536424b0c8 | ||
|
|
0859e38bd5 | ||
|
|
46bbd78b23 | ||
|
|
5c474ec42d | ||
|
|
2df2f35423 | ||
|
|
216075cc72 | ||
|
|
f5fba333d9 | ||
|
|
aad14b8b87 | ||
|
|
8bcaf832ae | ||
|
|
aac01cb096 | ||
|
|
e72fefa74d | ||
|
|
7617b8af52 | ||
|
|
33fd9c7c8f | ||
|
|
8703124c76 | ||
|
|
4ee2c311a7 | ||
|
|
020593d509 | ||
|
|
1f118c4b84 | ||
|
|
abbc6a2587 | ||
|
|
5a26d4c039 | ||
|
|
94950cccc8 | ||
|
|
3a00077305 | ||
|
|
4a82606ffb | ||
|
|
d5f63ebac4 | ||
|
|
dce079e711 | ||
|
|
632525f4d0 | ||
|
|
98692523bf | ||
|
|
87534692d0 | ||
|
|
a358174536 | ||
|
|
ff32f90a29 | ||
|
|
fb69620e49 | ||
|
|
d219f244d2 | ||
|
|
b0860ce5f0 | ||
|
|
56a2c587ad | ||
|
|
c43eceb2cb | ||
|
|
799e1f0469 | ||
|
|
fdcf332a8a | ||
|
|
bf3329e9a9 | ||
|
|
6b26154077 | ||
|
|
5546ecd637 | ||
|
|
079d4039a1 | ||
|
|
1ed0c7d85d | ||
|
|
3979387c80 | ||
|
|
f86b645417 | ||
|
|
688d706449 | ||
|
|
028597f774 | ||
|
|
0550baaf4f | ||
|
|
c33b171043 | ||
|
|
9d67d229fa | ||
|
|
cdbf2f9293 | ||
|
|
5def6ebc3b | ||
|
|
ea62deda59 | ||
|
|
3e43f4e58e | ||
|
|
2e08766cb1 | ||
|
|
e476b9d225 | ||
|
|
b2b836d4c1 | ||
|
|
5e81736f88 | ||
|
|
37427d052e | ||
|
|
5656b0eb2c | ||
|
|
b854cdb95b | ||
|
|
be31a860d1 | ||
|
|
0409192e64 | ||
|
|
c036141b37 | ||
|
|
b8e4c2ff69 | ||
|
|
cc236529c4 | ||
|
|
dacd7cd8a4 | ||
|
|
8a301c6c59 | ||
|
|
6918993c75 | ||
|
|
88161cd5c9 |
60
.coveragerc
60
.coveragerc
@@ -29,6 +29,9 @@ omit =
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
homeassistant/components/bmw_connected_drive.py
|
||||
homeassistant/components/*/bmw_connected_drive.py
|
||||
|
||||
homeassistant/components/android_ip_webcam.py
|
||||
homeassistant/components/*/android_ip_webcam.py
|
||||
|
||||
@@ -38,6 +41,9 @@ omit =
|
||||
homeassistant/components/asterisk_mbox.py
|
||||
homeassistant/components/*/asterisk_mbox.py
|
||||
|
||||
homeassistant/components/august.py
|
||||
homeassistant/components/*/august.py
|
||||
|
||||
homeassistant/components/axis.py
|
||||
homeassistant/components/*/axis.py
|
||||
|
||||
@@ -56,6 +62,9 @@ omit =
|
||||
homeassistant/components/comfoconnect.py
|
||||
homeassistant/components/*/comfoconnect.py
|
||||
|
||||
homeassistant/components/daikin.py
|
||||
homeassistant/components/*/daikin.py
|
||||
|
||||
homeassistant/components/deconz/*
|
||||
homeassistant/components/*/deconz.py
|
||||
|
||||
@@ -76,6 +85,9 @@ omit =
|
||||
homeassistant/components/ecobee.py
|
||||
homeassistant/components/*/ecobee.py
|
||||
|
||||
homeassistant/components/egardia.py
|
||||
homeassistant/components/*/egardia.py
|
||||
|
||||
homeassistant/components/enocean.py
|
||||
homeassistant/components/*/enocean.py
|
||||
|
||||
@@ -97,6 +109,9 @@ omit =
|
||||
homeassistant/components/homematic/__init__.py
|
||||
homeassistant/components/*/homematic.py
|
||||
|
||||
homeassistant/components/ihc/*
|
||||
homeassistant/components/*/ihc.py
|
||||
|
||||
homeassistant/components/insteon_local.py
|
||||
homeassistant/components/*/insteon_local.py
|
||||
|
||||
@@ -106,6 +121,9 @@ omit =
|
||||
homeassistant/components/ios.py
|
||||
homeassistant/components/*/ios.py
|
||||
|
||||
homeassistant/components/iota.py
|
||||
homeassistant/components/*/iota.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
@@ -139,12 +157,18 @@ omit =
|
||||
homeassistant/components/maxcube.py
|
||||
homeassistant/components/*/maxcube.py
|
||||
|
||||
homeassistant/components/mercedesme.py
|
||||
homeassistant/components/*/mercedesme.py
|
||||
|
||||
homeassistant/components/mochad.py
|
||||
homeassistant/components/*/mochad.py
|
||||
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/mychevy.py
|
||||
homeassistant/components/*/mychevy.py
|
||||
|
||||
homeassistant/components/mysensors.py
|
||||
homeassistant/components/*/mysensors.py
|
||||
|
||||
@@ -163,6 +187,9 @@ omit =
|
||||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/pilight.py
|
||||
homeassistant/components/*/pilight.py
|
||||
|
||||
homeassistant/components/qwikswitch.py
|
||||
homeassistant/components/*/qwikswitch.py
|
||||
|
||||
@@ -193,6 +220,9 @@ omit =
|
||||
homeassistant/components/skybell.py
|
||||
homeassistant/components/*/skybell.py
|
||||
|
||||
homeassistant/components/smappee.py
|
||||
homeassistant/components/*/smappee.py
|
||||
|
||||
homeassistant/components/tado.py
|
||||
homeassistant/components/*/tado.py
|
||||
|
||||
@@ -223,6 +253,9 @@ omit =
|
||||
homeassistant/components/notify/twilio_sms.py
|
||||
homeassistant/components/notify/twilio_call.py
|
||||
|
||||
homeassistant/components/upcloud.py
|
||||
homeassistant/components/*/upcloud.py
|
||||
|
||||
homeassistant/components/usps.py
|
||||
homeassistant/components/*/usps.py
|
||||
|
||||
@@ -241,6 +274,9 @@ omit =
|
||||
homeassistant/components/volvooncall.py
|
||||
homeassistant/components/*/volvooncall.py
|
||||
|
||||
homeassistant/components/waterfurnace.py
|
||||
homeassistant/components/*/waterfurnace.py
|
||||
|
||||
homeassistant/components/*/webostv.py
|
||||
|
||||
homeassistant/components/wemo.py
|
||||
@@ -269,13 +305,9 @@ omit =
|
||||
homeassistant/components/zoneminder.py
|
||||
homeassistant/components/*/zoneminder.py
|
||||
|
||||
homeassistant/components/daikin.py
|
||||
homeassistant/components/*/daikin.py
|
||||
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/canary.py
|
||||
homeassistant/components/alarm_control_panel/concord232.py
|
||||
homeassistant/components/alarm_control_panel/egardia.py
|
||||
homeassistant/components/alarm_control_panel/ialarm.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
@@ -288,7 +320,6 @@ omit =
|
||||
homeassistant/components/binary_sensor/hikvision.py
|
||||
homeassistant/components/binary_sensor/iss.py
|
||||
homeassistant/components/binary_sensor/mystrom.py
|
||||
homeassistant/components/binary_sensor/pilight.py
|
||||
homeassistant/components/binary_sensor/ping.py
|
||||
homeassistant/components/binary_sensor/rest.py
|
||||
homeassistant/components/binary_sensor/tapsaff.py
|
||||
@@ -304,6 +335,7 @@ omit =
|
||||
homeassistant/components/camera/ring.py
|
||||
homeassistant/components/camera/rpi_camera.py
|
||||
homeassistant/components/camera/synology.py
|
||||
homeassistant/components/camera/xeoma.py
|
||||
homeassistant/components/camera/yi.py
|
||||
homeassistant/components/climate/econet.py
|
||||
homeassistant/components/climate/ephember.py
|
||||
@@ -318,6 +350,7 @@ omit =
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/climate/sensibo.py
|
||||
homeassistant/components/climate/touchline.py
|
||||
homeassistant/components/climate/venstar.py
|
||||
homeassistant/components/cover/garadget.py
|
||||
homeassistant/components/cover/homematic.py
|
||||
homeassistant/components/cover/knx.py
|
||||
@@ -334,6 +367,7 @@ omit =
|
||||
homeassistant/components/device_tracker/bluetooth_tracker.py
|
||||
homeassistant/components/device_tracker/bt_home_hub_5.py
|
||||
homeassistant/components/device_tracker/cisco_ios.py
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/gpslogger.py
|
||||
homeassistant/components/device_tracker/hitron_coda.py
|
||||
@@ -364,6 +398,7 @@ omit =
|
||||
homeassistant/components/fan/xiaomi_miio.py
|
||||
homeassistant/components/feedreader.py
|
||||
homeassistant/components/foursquare.py
|
||||
homeassistant/components/goalfeed.py
|
||||
homeassistant/components/ifttt.py
|
||||
homeassistant/components/image_processing/dlib_face_detect.py
|
||||
homeassistant/components/image_processing/dlib_face_identify.py
|
||||
@@ -422,6 +457,7 @@ omit =
|
||||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/lg_netcast.py
|
||||
homeassistant/components/media_player/liveboxplaytv.py
|
||||
homeassistant/components/media_player/mediaroom.py
|
||||
homeassistant/components/media_player/mpchc.py
|
||||
homeassistant/components/media_player/mpd.py
|
||||
homeassistant/components/media_player/nad.py
|
||||
@@ -436,8 +472,8 @@ omit =
|
||||
homeassistant/components/media_player/roku.py
|
||||
homeassistant/components/media_player/russound_rio.py
|
||||
homeassistant/components/media_player/russound_rnet.py
|
||||
homeassistant/components/media_player/samsungtv.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/songpal.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/spotify.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
@@ -445,6 +481,7 @@ omit =
|
||||
homeassistant/components/media_player/vizio.py
|
||||
homeassistant/components/media_player/vlc.py
|
||||
homeassistant/components/media_player/volumio.py
|
||||
homeassistant/components/media_player/xiaomi_tv.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
homeassistant/components/media_player/yamaha_musiccast.py
|
||||
homeassistant/components/media_player/ziggo_mediabox_xl.py
|
||||
@@ -481,6 +518,7 @@ omit =
|
||||
homeassistant/components/notify/simplepush.py
|
||||
homeassistant/components/notify/slack.py
|
||||
homeassistant/components/notify/smtp.py
|
||||
homeassistant/components/notify/synology_chat.py
|
||||
homeassistant/components/notify/syslog.py
|
||||
homeassistant/components/notify/telegram.py
|
||||
homeassistant/components/notify/telstra.py
|
||||
@@ -493,6 +531,7 @@ omit =
|
||||
homeassistant/components/remember_the_milk/__init__.py
|
||||
homeassistant/components/remote/harmony.py
|
||||
homeassistant/components/remote/itach.py
|
||||
homeassistant/components/remote/xiaomi_miio.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/scene/lifx_cloud.py
|
||||
homeassistant/components/sensor/airvisual.py
|
||||
@@ -504,6 +543,7 @@ omit =
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/blockchain.py
|
||||
homeassistant/components/sensor/bme280.py
|
||||
homeassistant/components/sensor/bme680.py
|
||||
homeassistant/components/sensor/bom.py
|
||||
homeassistant/components/sensor/broadlink.py
|
||||
homeassistant/components/sensor/buienradar.py
|
||||
@@ -532,8 +572,10 @@ omit =
|
||||
homeassistant/components/sensor/etherscan.py
|
||||
homeassistant/components/sensor/fastdotcom.py
|
||||
homeassistant/components/sensor/fedex.py
|
||||
homeassistant/components/sensor/filesize.py
|
||||
homeassistant/components/sensor/fitbit.py
|
||||
homeassistant/components/sensor/fixer.py
|
||||
homeassistant/components/sensor/folder.py
|
||||
homeassistant/components/sensor/fritzbox_callmonitor.py
|
||||
homeassistant/components/sensor/fritzbox_netmonitor.py
|
||||
homeassistant/components/sensor/gearbest.py
|
||||
@@ -578,6 +620,7 @@ omit =
|
||||
homeassistant/components/sensor/pi_hole.py
|
||||
homeassistant/components/sensor/plex.py
|
||||
homeassistant/components/sensor/pocketcasts.py
|
||||
homeassistant/components/sensor/pollen.py
|
||||
homeassistant/components/sensor/pushbullet.py
|
||||
homeassistant/components/sensor/pvoutput.py
|
||||
homeassistant/components/sensor/pyload.py
|
||||
@@ -587,16 +630,19 @@ omit =
|
||||
homeassistant/components/sensor/ripple.py
|
||||
homeassistant/components/sensor/sabnzbd.py
|
||||
homeassistant/components/sensor/scrape.py
|
||||
homeassistant/components/sensor/sense.py
|
||||
homeassistant/components/sensor/sensehat.py
|
||||
homeassistant/components/sensor/serial.py
|
||||
homeassistant/components/sensor/serial_pm.py
|
||||
homeassistant/components/sensor/shodan.py
|
||||
homeassistant/components/sensor/simulated.py
|
||||
homeassistant/components/sensor/skybeacon.py
|
||||
homeassistant/components/sensor/sma.py
|
||||
homeassistant/components/sensor/snmp.py
|
||||
homeassistant/components/sensor/sochain.py
|
||||
homeassistant/components/sensor/sonarr.py
|
||||
homeassistant/components/sensor/speedtest.py
|
||||
homeassistant/components/sensor/spotcrime.py
|
||||
homeassistant/components/sensor/steam_online.py
|
||||
homeassistant/components/sensor/supervisord.py
|
||||
homeassistant/components/sensor/swiss_hydrological_data.py
|
||||
@@ -624,6 +670,7 @@ omit =
|
||||
homeassistant/components/sensor/worxlandroid.py
|
||||
homeassistant/components/sensor/xbox_live.py
|
||||
homeassistant/components/sensor/zamg.py
|
||||
homeassistant/components/sensor/zestimate.py
|
||||
homeassistant/components/shiftr.py
|
||||
homeassistant/components/spc.py
|
||||
homeassistant/components/switch/acer_projector.py
|
||||
@@ -641,7 +688,6 @@ omit =
|
||||
homeassistant/components/switch/mystrom.py
|
||||
homeassistant/components/switch/netio.py
|
||||
homeassistant/components/switch/orvibo.py
|
||||
homeassistant/components/switch/pilight.py
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rainbird.py
|
||||
homeassistant/components/switch/rainmachine.py
|
||||
|
||||
11
.gitattributes
vendored
11
.gitattributes
vendored
@@ -1,3 +1,10 @@
|
||||
# Ensure Docker script files uses LF to support Docker for Windows.
|
||||
setup_docker_prereqs eol=lf
|
||||
/virtualization/Docker/scripts/* eol=lf
|
||||
# Ensure "git config --global core.autocrlf input" before you clone
|
||||
* text eol=lf
|
||||
*.py whitespace=error
|
||||
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.zip binary
|
||||
*.mp3 binary
|
||||
|
||||
13
.github/move.yml
vendored
Normal file
13
.github/move.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Configuration for move-issues - https://github.com/dessant/move-issues
|
||||
|
||||
# Delete the command comment. Ignored when the comment also contains other content
|
||||
deleteCommand: true
|
||||
# Close the source issue after moving
|
||||
closeSourceIssue: true
|
||||
# Lock the source issue after moving
|
||||
lockSourceIssue: false
|
||||
# Set custom aliases for targets
|
||||
# aliases:
|
||||
# r: repo
|
||||
# or: owner/repo
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -16,7 +16,9 @@ Icon
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea
|
||||
*.iml
|
||||
|
||||
# pytest
|
||||
.cache
|
||||
@@ -98,3 +100,9 @@ desktop.ini
|
||||
/home-assistant.pyproj
|
||||
/home-assistant.sln
|
||||
/.vs/*
|
||||
|
||||
# mypy
|
||||
/.mypy_cache/*
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@@ -6,12 +6,10 @@ addons:
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "3.4.2"
|
||||
- python: "3.5.3"
|
||||
env: TOXENV=lint
|
||||
- python: "3.4.2"
|
||||
- python: "3.5.3"
|
||||
env: TOXENV=pylint
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=py34
|
||||
# - python: "3.5"
|
||||
# env: TOXENV=typing
|
||||
- python: "3.5.3"
|
||||
@@ -30,4 +28,15 @@ cache:
|
||||
install: pip install -U tox coveralls
|
||||
language: python
|
||||
script: travis_wait 30 tox --develop
|
||||
services:
|
||||
- docker
|
||||
before_deploy:
|
||||
- docker pull lokalise/lokalise-cli@sha256:79b3108211ed1fcc9f7b09a011bfc53c240fc2f3b7fa7f0c8390f593271b4cd7
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
provider: script
|
||||
script: script/travis_deploy
|
||||
on:
|
||||
branch: dev
|
||||
condition: $TOXENV = lint
|
||||
after_success: coveralls
|
||||
|
||||
17
CODEOWNERS
17
CODEOWNERS
@@ -32,14 +32,19 @@ homeassistant/components/zone.py @home-assistant/core
|
||||
# To monitor non-pypi additions
|
||||
requirements_all.txt @andrey-git
|
||||
|
||||
# HomeAssistant 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
|
||||
|
||||
# Indiviudal components
|
||||
homeassistant/components/hassio.py @home-assistant/hassio
|
||||
|
||||
# Individual components
|
||||
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
|
||||
homeassistant/components/binary_sensor/hikvision.py @mezz64
|
||||
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/camera/yi.py @bachya
|
||||
homeassistant/components/climate/ephember.py @ttroy50
|
||||
homeassistant/components/climate/eq3btsmart.py @rytilahti
|
||||
@@ -50,15 +55,21 @@ homeassistant/components/device_tracker/tile.py @bachya
|
||||
homeassistant/components/history_graph.py @andrey-git
|
||||
homeassistant/components/light/tplink.py @rytilahti
|
||||
homeassistant/components/light/yeelight.py @rytilahti
|
||||
homeassistant/components/media_player/emby.py @mezz64
|
||||
homeassistant/components/media_player/kodi.py @armills
|
||||
homeassistant/components/media_player/mediaroom.py @dgomes
|
||||
homeassistant/components/media_player/monoprice.py @etsinko
|
||||
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/plant.py @ChristianKuehnel
|
||||
homeassistant/components/sensor/airvisual.py @bachya
|
||||
homeassistant/components/sensor/gearbest.py @HerrHofrat
|
||||
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
|
||||
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
|
||||
homeassistant/components/sensor/pollen.py @bachya
|
||||
homeassistant/components/sensor/sytadin.py @gautric
|
||||
homeassistant/components/sensor/sql.py @dgomes
|
||||
homeassistant/components/sensor/tibber.py @danielhiversen
|
||||
homeassistant/components/sensor/waqi.py @andrey-git
|
||||
homeassistant/components/switch/rainmachine.py @bachya
|
||||
@@ -66,9 +77,13 @@ homeassistant/components/switch/tplink.py @rytilahti
|
||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||
|
||||
homeassistant/components/*/axis.py @kane610
|
||||
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/*/broadlink.py @danielhiversen
|
||||
homeassistant/components/eight_sleep.py @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
homeassistant/components/hive.py @Rendili @KJonline
|
||||
homeassistant/components/*/hive.py @Rendili @KJonline
|
||||
homeassistant/components/homekit/* @cdce8p
|
||||
homeassistant/components/*/deconz.py @kane610
|
||||
homeassistant/components/*/rfxtrx.py @danielhiversen
|
||||
homeassistant/components/velux.py @Julius2342
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<li><a href="https://home-assistant.io/">Homepage</a></li>
|
||||
<li><a href="https://community.home-assistant.io">Community Forums</a></li>
|
||||
<li><a href="https://github.com/home-assistant/home-assistant">GitHub</a></li>
|
||||
<li><a href="https://gitter.im/home-assistant/home-assistant">Gitter</a></li>
|
||||
<li><a href="https://discord.gg/c5DvZ4e">Discord</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -22,10 +22,23 @@ import os
|
||||
import inspect
|
||||
|
||||
from homeassistant.const import __version__, __short_version__
|
||||
from setup import (
|
||||
PROJECT_NAME, PROJECT_LONG_DESCRIPTION, PROJECT_COPYRIGHT, PROJECT_AUTHOR,
|
||||
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY, GITHUB_PATH,
|
||||
GITHUB_URL)
|
||||
|
||||
PROJECT_NAME = 'Home Assistant'
|
||||
PROJECT_PACKAGE_NAME = 'homeassistant'
|
||||
PROJECT_AUTHOR = 'The Home Assistant Authors'
|
||||
PROJECT_COPYRIGHT = ' 2013-2018, {}'.format(PROJECT_AUTHOR)
|
||||
PROJECT_LONG_DESCRIPTION = ('Home Assistant is an open-source '
|
||||
'home automation platform running on Python 3. '
|
||||
'Track and control all devices at home and '
|
||||
'automate control. '
|
||||
'Installation in less than a minute.')
|
||||
PROJECT_GITHUB_USERNAME = 'home-assistant'
|
||||
PROJECT_GITHUB_REPOSITORY = 'home-assistant'
|
||||
|
||||
GITHUB_PATH = '{}/{}'.format(
|
||||
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
|
||||
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
|
||||
|
||||
|
||||
sys.path.insert(0, os.path.abspath('_ext'))
|
||||
sys.path.insert(0, os.path.abspath('../homeassistant'))
|
||||
|
||||
@@ -15,7 +15,6 @@ from homeassistant.const import (
|
||||
__version__,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
REQUIRED_PYTHON_VER,
|
||||
REQUIRED_PYTHON_VER_WIN,
|
||||
RESTART_EXIT_CODE,
|
||||
)
|
||||
|
||||
@@ -33,12 +32,7 @@ def attempt_use_uvloop():
|
||||
|
||||
def validate_python() -> None:
|
||||
"""Validate that the right Python version is running."""
|
||||
if sys.platform == "win32" and \
|
||||
sys.version_info[:3] < REQUIRED_PYTHON_VER_WIN:
|
||||
print("Home Assistant requires at least Python {}.{}.{}".format(
|
||||
*REQUIRED_PYTHON_VER_WIN))
|
||||
sys.exit(1)
|
||||
elif sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||
print("Home Assistant requires at least Python {}.{}.{}".format(
|
||||
*REQUIRED_PYTHON_VER))
|
||||
sys.exit(1)
|
||||
@@ -182,7 +176,8 @@ def check_pid(pid_file: str) -> None:
|
||||
"""Check that Home Assistant is not already running."""
|
||||
# Check pid file
|
||||
try:
|
||||
pid = int(open(pid_file, 'r').readline())
|
||||
with open(pid_file, 'r') as file:
|
||||
pid = int(file.readline())
|
||||
except IOError:
|
||||
# PID File does not exist
|
||||
return
|
||||
@@ -204,7 +199,8 @@ def write_pid(pid_file: str) -> None:
|
||||
"""Create a PID File."""
|
||||
pid = os.getpid()
|
||||
try:
|
||||
open(pid_file, 'w').write(str(pid))
|
||||
with open(pid_file, 'w') as file:
|
||||
file.write(str(pid))
|
||||
except IOError:
|
||||
print('Fatal Error: Unable to write pid file {}'.format(pid_file))
|
||||
sys.exit(1)
|
||||
|
||||
@@ -12,7 +12,8 @@ from typing import Any, Optional, Dict
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import (
|
||||
core, config as conf_util, loader, components as core_components)
|
||||
core, config as conf_util, config_entries, loader,
|
||||
components as core_components)
|
||||
from homeassistant.components import persistent_notification
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
||||
from homeassistant.setup import async_setup_component
|
||||
@@ -35,13 +36,13 @@ FIRST_INIT_COMPONENT = set((
|
||||
|
||||
|
||||
def from_config_dict(config: Dict[str, Any],
|
||||
hass: Optional[core.HomeAssistant]=None,
|
||||
config_dir: Optional[str]=None,
|
||||
enable_log: bool=True,
|
||||
verbose: bool=False,
|
||||
skip_pip: bool=False,
|
||||
log_rotate_days: Any=None,
|
||||
log_file: Any=None) \
|
||||
hass: Optional[core.HomeAssistant] = None,
|
||||
config_dir: Optional[str] = None,
|
||||
enable_log: bool = True,
|
||||
verbose: bool = False,
|
||||
skip_pip: bool = False,
|
||||
log_rotate_days: Any = None,
|
||||
log_file: Any = None) \
|
||||
-> Optional[core.HomeAssistant]:
|
||||
"""Try to configure Home Assistant from a configuration dictionary.
|
||||
|
||||
@@ -68,12 +69,12 @@ def from_config_dict(config: Dict[str, Any],
|
||||
@asyncio.coroutine
|
||||
def async_from_config_dict(config: Dict[str, Any],
|
||||
hass: core.HomeAssistant,
|
||||
config_dir: Optional[str]=None,
|
||||
enable_log: bool=True,
|
||||
verbose: bool=False,
|
||||
skip_pip: bool=False,
|
||||
log_rotate_days: Any=None,
|
||||
log_file: Any=None) \
|
||||
config_dir: Optional[str] = None,
|
||||
enable_log: bool = True,
|
||||
verbose: bool = False,
|
||||
skip_pip: bool = False,
|
||||
log_rotate_days: Any = None,
|
||||
log_file: Any = None) \
|
||||
-> Optional[core.HomeAssistant]:
|
||||
"""Try to configure Home Assistant from a configuration dictionary.
|
||||
|
||||
@@ -111,21 +112,20 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
if not loader.PREPARED:
|
||||
yield from hass.async_add_job(loader.prepare, hass)
|
||||
|
||||
# Make a copy because we are mutating it.
|
||||
config = OrderedDict(config)
|
||||
|
||||
# Merge packages
|
||||
conf_util.merge_packages_config(
|
||||
config, core_config.get(conf_util.CONF_PACKAGES, {}))
|
||||
|
||||
# Make a copy because we are mutating it.
|
||||
# Use OrderedDict in case original one was one.
|
||||
# Convert values to dictionaries if they are None
|
||||
new_config = OrderedDict()
|
||||
for key, value in config.items():
|
||||
new_config[key] = value or {}
|
||||
config = new_config
|
||||
hass.config_entries = config_entries.ConfigEntries(hass, config)
|
||||
yield from hass.config_entries.async_load()
|
||||
|
||||
# Filter out the repeating and common config section [homeassistant]
|
||||
components = set(key.split(' ')[0] for key in config.keys()
|
||||
if key != core.DOMAIN)
|
||||
components.update(hass.config_entries.async_domains())
|
||||
|
||||
# setup components
|
||||
# pylint: disable=not-an-iterable
|
||||
@@ -163,11 +163,11 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
|
||||
|
||||
def from_config_file(config_path: str,
|
||||
hass: Optional[core.HomeAssistant]=None,
|
||||
verbose: bool=False,
|
||||
skip_pip: bool=True,
|
||||
log_rotate_days: Any=None,
|
||||
log_file: Any=None):
|
||||
hass: Optional[core.HomeAssistant] = None,
|
||||
verbose: bool = False,
|
||||
skip_pip: bool = True,
|
||||
log_rotate_days: Any = None,
|
||||
log_file: Any = None):
|
||||
"""Read the configuration file and try to start all the functionality.
|
||||
|
||||
Will add functionality to 'hass' parameter if given,
|
||||
@@ -188,10 +188,10 @@ def from_config_file(config_path: str,
|
||||
@asyncio.coroutine
|
||||
def async_from_config_file(config_path: str,
|
||||
hass: core.HomeAssistant,
|
||||
verbose: bool=False,
|
||||
skip_pip: bool=True,
|
||||
log_rotate_days: Any=None,
|
||||
log_file: Any=None):
|
||||
verbose: bool = False,
|
||||
skip_pip: bool = True,
|
||||
log_rotate_days: Any = None,
|
||||
log_file: Any = None):
|
||||
"""Read the configuration file and try to start all the functionality.
|
||||
|
||||
Will add functionality to 'hass' parameter.
|
||||
@@ -219,7 +219,7 @@ def async_from_config_file(config_path: str,
|
||||
|
||||
|
||||
@core.callback
|
||||
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
|
||||
def async_enable_logging(hass: core.HomeAssistant, verbose: bool = False,
|
||||
log_rotate_days=None, log_file=None) -> None:
|
||||
"""Set up the logging.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import homeassistant.core as ha
|
||||
import homeassistant.config as conf_util
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.service import extract_entity_ids
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
||||
@@ -133,7 +134,7 @@ def async_setup(hass, config):
|
||||
# have been processed. If a service does not exist it causes a 10
|
||||
# second delay while we're blocking waiting for a response.
|
||||
# But services can be registered on other HA instances that are
|
||||
# listening to the bus too. So as a in between solution, we'll
|
||||
# listening to the bus too. So as an in between solution, we'll
|
||||
# block only if the service is defined in the current HA instance.
|
||||
blocking = hass.services.has_service(domain, service.service)
|
||||
|
||||
@@ -154,6 +155,13 @@ def async_setup(hass, config):
|
||||
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
|
||||
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on"))
|
||||
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF,
|
||||
"Turned {} off"))
|
||||
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_core_service(call):
|
||||
|
||||
@@ -7,6 +7,7 @@ https://home-assistant.io/components/abode/
|
||||
import asyncio
|
||||
import logging
|
||||
from functools import partial
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -17,7 +18,6 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
|
||||
REQUIREMENTS = ['abodepy==0.12.2']
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""
|
||||
ADS Component.
|
||||
Support for Automation Device Specification (ADS).
|
||||
|
||||
For more details about this component, please refer to the documentation.
|
||||
https://home-assistant.io/components/ads/
|
||||
|
||||
"""
|
||||
import threading
|
||||
import struct
|
||||
@@ -29,7 +28,6 @@ ADSTYPE_BOOL = 'bool'
|
||||
|
||||
DOMAIN = 'ads'
|
||||
|
||||
# config variable names
|
||||
CONF_ADS_VAR = 'adsvar'
|
||||
CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness'
|
||||
CONF_ADS_TYPE = 'adstype'
|
||||
@@ -47,10 +45,10 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
|
||||
vol.Required(CONF_ADS_TYPE):
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]),
|
||||
vol.Required(CONF_ADS_VALUE): cv.match_all,
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT,
|
||||
ADSTYPE_BYTE]),
|
||||
vol.Required(CONF_ADS_VALUE): cv.match_all
|
||||
})
|
||||
|
||||
|
||||
@@ -59,15 +57,12 @@ def setup(hass, config):
|
||||
import pyads
|
||||
conf = config[DOMAIN]
|
||||
|
||||
# get ads connection parameters from config
|
||||
net_id = conf.get(CONF_DEVICE)
|
||||
ip_address = conf.get(CONF_IP_ADDRESS)
|
||||
port = conf.get(CONF_PORT)
|
||||
|
||||
# create a new ads connection
|
||||
client = pyads.Connection(net_id, port, ip_address)
|
||||
|
||||
# add some constants to AdsHub
|
||||
AdsHub.ADS_TYPEMAP = {
|
||||
ADSTYPE_BOOL: pyads.PLCTYPE_BOOL,
|
||||
ADSTYPE_BYTE: pyads.PLCTYPE_BYTE,
|
||||
@@ -81,16 +76,13 @@ def setup(hass, config):
|
||||
AdsHub.PLCTYPE_UINT = pyads.PLCTYPE_UINT
|
||||
AdsHub.ADSError = pyads.ADSError
|
||||
|
||||
# connect to ads client and try to connect
|
||||
try:
|
||||
ads = AdsHub(client)
|
||||
except pyads.pyads.ADSError:
|
||||
_LOGGER.error(
|
||||
'Could not connect to ADS host (netid=%s, port=%s)', net_id, port
|
||||
)
|
||||
"Could not connect to ADS host (netid=%s, port=%s)", net_id, port)
|
||||
return False
|
||||
|
||||
# add ads hub to hass data collection, listen to shutdown
|
||||
hass.data[DATA_ADS] = ads
|
||||
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, ads.shutdown)
|
||||
|
||||
@@ -107,43 +99,41 @@ def setup(hass, config):
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name,
|
||||
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME
|
||||
)
|
||||
schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# tuple to hold data needed for notification
|
||||
# Tuple to hold data needed for notification
|
||||
NotificationItem = namedtuple(
|
||||
'NotificationItem', 'hnotify huser name plc_datatype callback'
|
||||
)
|
||||
|
||||
|
||||
class AdsHub:
|
||||
"""Representation of a PyADS connection."""
|
||||
class AdsHub(object):
|
||||
"""Representation of an ADS connection."""
|
||||
|
||||
def __init__(self, ads_client):
|
||||
"""Initialize the ADS Hub."""
|
||||
"""Initialize the ADS hub."""
|
||||
self._client = ads_client
|
||||
self._client.open()
|
||||
|
||||
# all ADS devices are registered here
|
||||
# All ADS devices are registered here
|
||||
self._devices = []
|
||||
self._notification_items = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def shutdown(self, *args, **kwargs):
|
||||
"""Shutdown ADS connection."""
|
||||
_LOGGER.debug('Shutting down ADS')
|
||||
_LOGGER.debug("Shutting down ADS")
|
||||
for notification_item in self._notification_items.values():
|
||||
self._client.del_device_notification(
|
||||
notification_item.hnotify,
|
||||
notification_item.huser
|
||||
)
|
||||
_LOGGER.debug(
|
||||
'Deleting device notification %d, %d',
|
||||
notification_item.hnotify, notification_item.huser
|
||||
)
|
||||
"Deleting device notification %d, %d",
|
||||
notification_item.hnotify, notification_item.huser)
|
||||
self._client.close()
|
||||
|
||||
def register_device(self, device):
|
||||
@@ -167,33 +157,30 @@ class AdsHub:
|
||||
|
||||
with self._lock:
|
||||
hnotify, huser = self._client.add_device_notification(
|
||||
name, attr, self._device_notification_callback
|
||||
)
|
||||
name, attr, self._device_notification_callback)
|
||||
hnotify = int(hnotify)
|
||||
|
||||
_LOGGER.debug(
|
||||
'Added Device Notification %d for variable %s', hnotify, name
|
||||
)
|
||||
"Added device notification %d for variable %s", hnotify, name)
|
||||
|
||||
self._notification_items[hnotify] = NotificationItem(
|
||||
hnotify, huser, name, plc_datatype, callback
|
||||
)
|
||||
hnotify, huser, name, plc_datatype, callback)
|
||||
|
||||
def _device_notification_callback(self, addr, notification, huser):
|
||||
"""Handle device notifications."""
|
||||
contents = notification.contents
|
||||
|
||||
hnotify = int(contents.hNotification)
|
||||
_LOGGER.debug('Received Notification %d', hnotify)
|
||||
_LOGGER.debug("Received notification %d", hnotify)
|
||||
data = contents.data
|
||||
|
||||
try:
|
||||
notification_item = self._notification_items[hnotify]
|
||||
except KeyError:
|
||||
_LOGGER.debug('Unknown Device Notification handle: %d', hnotify)
|
||||
_LOGGER.debug("Unknown device notification handle: %d", hnotify)
|
||||
return
|
||||
|
||||
# parse data to desired datatype
|
||||
# Parse data to desired datatype
|
||||
if notification_item.plc_datatype == self.PLCTYPE_BOOL:
|
||||
value = bool(struct.unpack('<?', bytearray(data)[:1])[0])
|
||||
elif notification_item.plc_datatype == self.PLCTYPE_INT:
|
||||
@@ -204,7 +191,6 @@ class AdsHub:
|
||||
value = struct.unpack('<H', bytearray(data)[:2])[0]
|
||||
else:
|
||||
value = bytearray(data)
|
||||
_LOGGER.warning('No callback available for this datatype.')
|
||||
_LOGGER.warning("No callback available for this datatype")
|
||||
|
||||
# execute callback
|
||||
notification_item.callback(notification_item.name, value)
|
||||
|
||||
@@ -6,12 +6,12 @@ https://home-assistant.io/components/alarm_control_panel.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (
|
||||
AbodeDevice, DOMAIN as ABODE_DOMAIN, CONF_ATTRIBUTION)
|
||||
from homeassistant.components.alarm_control_panel import (AlarmControlPanel)
|
||||
from homeassistant.const import (ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
|
||||
from homeassistant.components.abode import CONF_ATTRIBUTION, AbodeDevice
|
||||
from homeassistant.components.abode import DOMAIN as ABODE_DOMAIN
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
@@ -21,7 +21,7 @@ ICON = 'mdi:security'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
"""Set up an alarm control panel for an Abode device."""
|
||||
data = hass.data[ABODE_DOMAIN]
|
||||
|
||||
alarm_devices = [AbodeAlarm(data, data.abode.get_alarm(), data.name)]
|
||||
@@ -41,7 +41,7 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
"""Return the icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
@@ -81,5 +81,5 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanel):
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
'device_id': self._device.device_id,
|
||||
'battery_backup': self._device.battery,
|
||||
'cellular_backup': self._device.is_cellular
|
||||
'cellular_backup': self._device.is_cellular,
|
||||
}
|
||||
|
||||
@@ -10,12 +10,11 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.alarmdecoder import (
|
||||
DATA_AD, SIGNAL_PANEL_MESSAGE)
|
||||
from homeassistant.components.alarmdecoder import DATA_AD, SIGNAL_PANEL_MESSAGE
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,6 +66,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
|
||||
def _message_callback(self, message):
|
||||
"""Handle received messages."""
|
||||
if message.alarm_sounding or message.fire_alarm:
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
elif message.armed_away:
|
||||
@@ -120,7 +120,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
'entry_delay_off': self._entry_delay_off,
|
||||
'programming_mode': self._programming_mode,
|
||||
'ready': self._ready,
|
||||
'zone_bypassed': self._zone_bypassed
|
||||
'zone_bypassed': self._zone_bypassed,
|
||||
}
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
|
||||
@@ -4,19 +4,20 @@ 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 logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE,
|
||||
CONF_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyalarmdotcom==0.3.0']
|
||||
REQUIREMENTS = ['pyalarmdotcom==0.3.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -44,7 +45,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
"""Represent an Alarm.com status."""
|
||||
"""Representation of an Alarm.com status."""
|
||||
|
||||
def __init__(self, hass, name, code, username, password):
|
||||
"""Initialize the Alarm.com status."""
|
||||
@@ -57,10 +58,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
self._password = password
|
||||
self._websession = async_get_clientsession(self._hass)
|
||||
self._state = STATE_UNKNOWN
|
||||
self._alarm = Alarmdotcom(username,
|
||||
password,
|
||||
self._websession,
|
||||
hass.loop)
|
||||
self._alarm = Alarmdotcom(
|
||||
username, password, self._websession, hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_login(self):
|
||||
@@ -80,7 +79,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters if code is defined."""
|
||||
"""Return one or more characters if code is defined."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
@property
|
||||
@@ -116,5 +115,5 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Wrong code entered.')
|
||||
_LOGGER.warning("Wrong code entered")
|
||||
return check
|
||||
|
||||
@@ -59,8 +59,7 @@ class CanaryAlarm(AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
elif mode.name == LOCATION_MODE_NIGHT:
|
||||
return STATE_ALARM_ARMED_NIGHT
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
||||
@@ -26,12 +26,12 @@ DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_NAME = 'CONCORD232'
|
||||
DEFAULT_PORT = 5007
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=1)
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
})
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
add_devices([Concord232Alarm(hass, url, name)])
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
|
||||
return False
|
||||
return
|
||||
|
||||
|
||||
class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
@@ -107,7 +107,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
newstate = STATE_ALARM_ARMED_AWAY
|
||||
|
||||
if not newstate == self._state:
|
||||
_LOGGER.info("State Change from %s to %s", self._state, newstate)
|
||||
_LOGGER.info("State change from %s to %s", self._state, newstate)
|
||||
self._state = newstate
|
||||
return self._state
|
||||
|
||||
|
||||
@@ -4,132 +4,65 @@ 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
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.exceptions as exc
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN,
|
||||
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED, EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
REQUIREMENTS = ['pythonegardia==1.0.26']
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
|
||||
from homeassistant.components.egardia import (
|
||||
EGARDIA_DEVICE, EGARDIA_SERVER,
|
||||
REPORT_SERVER_CODES_IGNORE, CONF_REPORT_SERVER_CODES,
|
||||
CONF_REPORT_SERVER_ENABLED, CONF_REPORT_SERVER_PORT
|
||||
)
|
||||
REQUIREMENTS = ['pythonegardia==1.0.38']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_REPORT_SERVER_CODES = 'report_server_codes'
|
||||
CONF_REPORT_SERVER_ENABLED = 'report_server_enabled'
|
||||
CONF_REPORT_SERVER_PORT = 'report_server_port'
|
||||
CONF_REPORT_SERVER_CODES_IGNORE = 'ignore'
|
||||
CONF_VERSION = 'version'
|
||||
|
||||
DEFAULT_NAME = 'Egardia'
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_REPORT_SERVER_ENABLED = False
|
||||
DEFAULT_REPORT_SERVER_PORT = 52010
|
||||
DEFAULT_VERSION = 'GATE-01'
|
||||
DOMAIN = 'egardia'
|
||||
D_EGARDIASRV = 'egardiaserver'
|
||||
NOTIFICATION_ID = 'egardia_notification'
|
||||
NOTIFICATION_TITLE = 'Egardia'
|
||||
|
||||
STATES = {
|
||||
'ARM': STATE_ALARM_ARMED_AWAY,
|
||||
'DAY HOME': STATE_ALARM_ARMED_HOME,
|
||||
'DISARM': STATE_ALARM_DISARMED,
|
||||
'HOME': STATE_ALARM_ARMED_HOME,
|
||||
'TRIGGERED': STATE_ALARM_TRIGGERED,
|
||||
'UNKNOWN': STATE_UNKNOWN,
|
||||
'ARMHOME': STATE_ALARM_ARMED_HOME,
|
||||
'TRIGGERED': STATE_ALARM_TRIGGERED
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_REPORT_SERVER_CODES): vol.All(cv.ensure_list),
|
||||
vol.Optional(CONF_REPORT_SERVER_ENABLED,
|
||||
default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean,
|
||||
vol.Optional(CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT):
|
||||
cv.port,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Egardia platform."""
|
||||
from pythonegardia import egardiadevice
|
||||
from pythonegardia import egardiaserver
|
||||
|
||||
name = config.get(CONF_NAME)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
rs_enabled = config.get(CONF_REPORT_SERVER_ENABLED)
|
||||
rs_port = config.get(CONF_REPORT_SERVER_PORT)
|
||||
rs_codes = config.get(CONF_REPORT_SERVER_CODES)
|
||||
version = config.get(CONF_VERSION)
|
||||
|
||||
try:
|
||||
egardiasystem = egardiadevice.EgardiaDevice(
|
||||
host, port, username, password, '', version)
|
||||
except requests.exceptions.RequestException:
|
||||
raise exc.PlatformNotReady()
|
||||
except egardiadevice.UnauthorizedError:
|
||||
_LOGGER.error("Unable to authorize. Wrong password or username")
|
||||
return
|
||||
|
||||
eg_dev = EgardiaAlarm(
|
||||
name, egardiasystem, rs_enabled, rs_codes)
|
||||
|
||||
if rs_enabled:
|
||||
# Set up the egardia server
|
||||
_LOGGER.info("Setting up EgardiaServer")
|
||||
try:
|
||||
if D_EGARDIASRV not in hass.data:
|
||||
server = egardiaserver.EgardiaServer('', rs_port)
|
||||
bound = server.bind()
|
||||
if not bound:
|
||||
raise IOError("Binding error occurred while " +
|
||||
"starting EgardiaServer")
|
||||
hass.data[D_EGARDIASRV] = server
|
||||
server.start()
|
||||
except IOError:
|
||||
return
|
||||
hass.data[D_EGARDIASRV].register_callback(eg_dev.handle_status_event)
|
||||
|
||||
def handle_stop_event(event):
|
||||
"""Callback function for HA stop event."""
|
||||
hass.data[D_EGARDIASRV].stop()
|
||||
|
||||
# listen to home assistant stop event
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
|
||||
|
||||
device = EgardiaAlarm(
|
||||
discovery_info['name'],
|
||||
hass.data[EGARDIA_DEVICE],
|
||||
discovery_info[CONF_REPORT_SERVER_ENABLED],
|
||||
discovery_info.get(CONF_REPORT_SERVER_CODES),
|
||||
discovery_info[CONF_REPORT_SERVER_PORT])
|
||||
# add egardia alarm device
|
||||
add_devices([eg_dev], True)
|
||||
add_devices([device], True)
|
||||
|
||||
|
||||
class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Egardia alarm."""
|
||||
|
||||
def __init__(self, name, egardiasystem,
|
||||
rs_enabled=False, rs_codes=None):
|
||||
"""Initialize object."""
|
||||
rs_enabled=False, rs_codes=None, rs_port=52010):
|
||||
"""Initialize the Egardia alarm."""
|
||||
self._name = name
|
||||
self._egardiasystem = egardiasystem
|
||||
self._status = None
|
||||
self._rs_enabled = rs_enabled
|
||||
if rs_codes is not None:
|
||||
self._rs_codes = rs_codes[0]
|
||||
else:
|
||||
self._rs_codes = rs_codes
|
||||
self._rs_codes = rs_codes
|
||||
self._rs_port = rs_port
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Add Egardiaserver callback if enabled."""
|
||||
if self._rs_enabled:
|
||||
_LOGGER.debug("Registering callback to Egardiaserver")
|
||||
self.hass.data[EGARDIA_SERVER].register_callback(
|
||||
self.handle_status_event)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -149,7 +82,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
return False
|
||||
|
||||
def handle_status_event(self, event):
|
||||
"""Handle egardia_system_status_event."""
|
||||
"""Handle the Egardia system status event."""
|
||||
statuscode = event.get('status')
|
||||
if statuscode is not None:
|
||||
status = self.lookupstatusfromcode(statuscode)
|
||||
@@ -158,31 +91,20 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def lookupstatusfromcode(self, statuscode):
|
||||
"""Look at the rs_codes and returns the status from the code."""
|
||||
status = 'UNKNOWN'
|
||||
if self._rs_codes is not None:
|
||||
statuscode = str(statuscode).strip()
|
||||
for i in self._rs_codes:
|
||||
val = str(self._rs_codes[i]).strip()
|
||||
if ',' in val:
|
||||
splitted = val.split(',')
|
||||
for code in splitted:
|
||||
code = str(code).strip()
|
||||
if statuscode == code:
|
||||
status = i.upper()
|
||||
break
|
||||
elif statuscode == val:
|
||||
status = i.upper()
|
||||
break
|
||||
status = next((
|
||||
status_group.upper() for status_group, codes
|
||||
in self._rs_codes.items() for code in codes
|
||||
if statuscode == code), 'UNKNOWN')
|
||||
return status
|
||||
|
||||
def parsestatus(self, status):
|
||||
"""Parse the status."""
|
||||
_LOGGER.debug("Parsing status %s", status)
|
||||
# Ignore the statuscode if it is IGNORE
|
||||
if status.lower().strip() != CONF_REPORT_SERVER_CODES_IGNORE:
|
||||
_LOGGER.debug("Not ignoring status")
|
||||
newstatus = ([v for k, v in STATES.items()
|
||||
if status.upper() == k][0])
|
||||
if status.lower().strip() != REPORT_SERVER_CODES_IGNORE:
|
||||
_LOGGER.debug("Not ignoring status %s", status)
|
||||
newstatus = STATES.get(status.upper())
|
||||
_LOGGER.debug("newstatus %s", newstatus)
|
||||
self._status = newstatus
|
||||
else:
|
||||
_LOGGER.error("Ignoring status")
|
||||
|
||||
@@ -8,12 +8,12 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, CONF_HOST, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, CONF_NAME)
|
||||
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyialarm==0.2']
|
||||
|
||||
@@ -33,9 +33,9 @@ def no_application_protocol(value):
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class IAlarmPanel(alarm.AlarmControlPanel):
|
||||
"""Represent an iAlarm status."""
|
||||
"""Representation of an iAlarm status."""
|
||||
|
||||
def __init__(self, name, username, password, url):
|
||||
"""Initialize the iAlarm status."""
|
||||
|
||||
@@ -11,15 +11,17 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
|
||||
STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, CONF_CODE,
|
||||
CONF_DELAY_TIME, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
|
||||
CONF_DISARM_AFTER_TRIGGER)
|
||||
CONF_CODE, CONF_DELAY_TIME, CONF_DISARM_AFTER_TRIGGER, CONF_NAME,
|
||||
CONF_PENDING_TIME, CONF_PLATFORM, CONF_TRIGGER_TIME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CODE_TEMPLATE = 'code_template'
|
||||
|
||||
@@ -44,6 +46,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
|
||||
|
||||
|
||||
def _state_validator(config):
|
||||
"""Validate the state."""
|
||||
config = copy.deepcopy(config)
|
||||
for state in SUPPORTED_PRETRIGGER_STATES:
|
||||
if CONF_DELAY_TIME not in config[state]:
|
||||
@@ -58,6 +61,7 @@ def _state_validator(config):
|
||||
|
||||
|
||||
def _state_schema(state):
|
||||
"""Validate the state."""
|
||||
schema = {}
|
||||
if state in SUPPORTED_PRETRIGGER_STATES:
|
||||
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
|
||||
@@ -97,8 +101,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All({
|
||||
_state_schema(STATE_ALARM_TRIGGERED),
|
||||
}, _state_validator))
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the manual alarm platform."""
|
||||
@@ -151,7 +153,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the plling state."""
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
@@ -170,9 +172,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
trigger_time) < dt_util.utcnow():
|
||||
if self._disarm_after_trigger:
|
||||
return STATE_ALARM_DISARMED
|
||||
else:
|
||||
self._state = self._previous_state
|
||||
return self._state
|
||||
self._state = self._previous_state
|
||||
return self._state
|
||||
|
||||
if self._state in SUPPORTED_PENDING_STATES and \
|
||||
self._within_pending_time(self._state):
|
||||
@@ -182,23 +183,25 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def _active_state(self):
|
||||
"""Get the current state."""
|
||||
if self.state == STATE_ALARM_PENDING:
|
||||
return self._previous_state
|
||||
else:
|
||||
return self._state
|
||||
return self._state
|
||||
|
||||
def _pending_time(self, state):
|
||||
"""Get the pending time."""
|
||||
pending_time = self._pending_time_by_state[state]
|
||||
if state == STATE_ALARM_TRIGGERED:
|
||||
pending_time += self._delay_time_by_state[self._previous_state]
|
||||
return pending_time
|
||||
|
||||
def _within_pending_time(self, state):
|
||||
"""Get if the action is in the pending time window."""
|
||||
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters."""
|
||||
"""Return one or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
@@ -250,6 +253,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._update_state(STATE_ALARM_TRIGGERED)
|
||||
|
||||
def _update_state(self, state):
|
||||
"""Update the state."""
|
||||
if self._state == state:
|
||||
return
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CODE_TEMPLATE = 'code_template'
|
||||
|
||||
CONF_PAYLOAD_DISARM = 'payload_disarm'
|
||||
@@ -58,6 +60,7 @@ ATTR_POST_PENDING_STATE = 'post_pending_state'
|
||||
|
||||
|
||||
def _state_validator(config):
|
||||
"""Validate the state."""
|
||||
config = copy.deepcopy(config)
|
||||
for state in SUPPORTED_PRETRIGGER_STATES:
|
||||
if CONF_DELAY_TIME not in config[state]:
|
||||
@@ -72,6 +75,7 @@ def _state_validator(config):
|
||||
|
||||
|
||||
def _state_schema(state):
|
||||
"""Validate the state."""
|
||||
schema = {}
|
||||
if state in SUPPORTED_PRETRIGGER_STATES:
|
||||
schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
|
||||
@@ -117,8 +121,6 @@ PLATFORM_SCHEMA = vol.Schema(vol.All(mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
|
||||
}), _state_validator))
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the manual MQTT alarm platform."""
|
||||
@@ -150,11 +152,10 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
A trigger_time of zero disables the alarm_trigger service.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, name, code, code_template,
|
||||
disarm_after_trigger,
|
||||
state_topic, command_topic, qos,
|
||||
payload_disarm, payload_arm_home, payload_arm_away,
|
||||
payload_arm_night, config):
|
||||
def __init__(self, hass, name, code, code_template, disarm_after_trigger,
|
||||
state_topic, command_topic, qos, payload_disarm,
|
||||
payload_arm_home, payload_arm_away, payload_arm_night,
|
||||
config):
|
||||
"""Init the manual MQTT alarm panel."""
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._hass = hass
|
||||
@@ -207,9 +208,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
trigger_time) < dt_util.utcnow():
|
||||
if self._disarm_after_trigger:
|
||||
return STATE_ALARM_DISARMED
|
||||
else:
|
||||
self._state = self._previous_state
|
||||
return self._state
|
||||
self._state = self._previous_state
|
||||
return self._state
|
||||
|
||||
if self._state in SUPPORTED_PENDING_STATES and \
|
||||
self._within_pending_time(self._state):
|
||||
@@ -219,23 +219,25 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def _active_state(self):
|
||||
"""Get the current state."""
|
||||
if self.state == STATE_ALARM_PENDING:
|
||||
return self._previous_state
|
||||
else:
|
||||
return self._state
|
||||
return self._state
|
||||
|
||||
def _pending_time(self, state):
|
||||
"""Get the pending time."""
|
||||
pending_time = self._pending_time_by_state[state]
|
||||
if state == STATE_ALARM_TRIGGERED:
|
||||
pending_time += self._delay_time_by_state[self._previous_state]
|
||||
return pending_time
|
||||
|
||||
def _within_pending_time(self, state):
|
||||
"""Get if the action is in the pending time window."""
|
||||
return self._state_ts + self._pending_time(state) > dt_util.utcnow()
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters."""
|
||||
"""Return one or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
@@ -280,6 +282,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
self._update_state(STATE_ALARM_TRIGGERED)
|
||||
|
||||
def _update_state(self, state):
|
||||
"""Update the state."""
|
||||
if self._state == state:
|
||||
return
|
||||
|
||||
@@ -329,7 +332,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
return state_attr
|
||||
|
||||
def async_added_to_hass(self):
|
||||
"""Subscribe mqtt events.
|
||||
"""Subscribe to MQTT events.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
@@ -358,5 +361,5 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
@asyncio.coroutine
|
||||
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)
|
||||
mqtt.async_publish(
|
||||
self.hass, self._state_topic, new_state.state, self._qos, True)
|
||||
|
||||
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
|
||||
})
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -12,8 +12,8 @@ import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN, CONF_NAME, CONF_HOST, CONF_PORT)
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pynx584==0.4']
|
||||
@@ -25,14 +25,14 @@ DEFAULT_NAME = 'NX584'
|
||||
DEFAULT_PORT = 5007
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the nx584 platform."""
|
||||
"""Set up the NX584 platform."""
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
@@ -88,7 +88,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
except IndexError:
|
||||
_LOGGER.error("nx584 reports no partitions")
|
||||
_LOGGER.error("NX584 reports no partitions")
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.satel_integra import (CONF_ARM_HOME_MODE,
|
||||
DATA_SATEL,
|
||||
SIGNAL_PANEL_MESSAGE)
|
||||
from homeassistant.components.satel_integra import (
|
||||
CONF_ARM_HOME_MODE, DATA_SATEL, SIGNAL_PANEL_MESSAGE)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
@@ -21,12 +20,12 @@ DEPENDENCIES = ['satel_integra']
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up for AlarmDecoder alarm panels."""
|
||||
"""Set up for Satel Integra alarm panels."""
|
||||
if not discovery_info:
|
||||
return
|
||||
|
||||
device = SatelIntegraAlarmPanel("Alarm Panel",
|
||||
discovery_info.get(CONF_ARM_HOME_MODE))
|
||||
device = SatelIntegraAlarmPanel(
|
||||
"Alarm Panel", discovery_info.get(CONF_ARM_HOME_MODE))
|
||||
async_add_devices([device])
|
||||
|
||||
|
||||
@@ -47,7 +46,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
|
||||
@callback
|
||||
def _message_callback(self, message):
|
||||
|
||||
"""Handle received messages."""
|
||||
if message != self._state:
|
||||
self._state = message
|
||||
self.async_schedule_update_ha_state()
|
||||
@@ -90,5 +89,5 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(code,
|
||||
self._arm_home_mode)
|
||||
yield from self.hass.data[DATA_SATEL].arm(
|
||||
code, self._arm_home_mode)
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
# Describes the format for available alarm control panel services
|
||||
|
||||
alarm_disarm:
|
||||
description: Send the alarm the command for disarm.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to disarm.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to disarm the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_home:
|
||||
description: Send the alarm the command for arm home.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm home.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm home the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_away:
|
||||
description: Send the alarm the command for arm away.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm away.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm away the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_night:
|
||||
description: Send the alarm the command for arm night.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm night.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm night the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_trigger:
|
||||
description: Send the alarm the command for trigger.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to trigger the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
envisalink_alarm_keypress:
|
||||
description: Send custom keypresses to the alarm.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of the alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
keypress:
|
||||
description: 'String to send to the alarm panel (1-6 characters).'
|
||||
example: '*71'
|
||||
|
||||
alarmdecoder_alarm_toggle_chime:
|
||||
description: Send the alarm the toggle chime command.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of the alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: A required code to toggle the alarm control panel chime with.
|
||||
example: 1234
|
||||
# Describes the format for available alarm control panel services
|
||||
|
||||
alarm_disarm:
|
||||
description: Send the alarm the command for disarm.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to disarm.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to disarm the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_home:
|
||||
description: Send the alarm the command for arm home.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm home.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm home the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_away:
|
||||
description: Send the alarm the command for arm away.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm away.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm away the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_arm_night:
|
||||
description: Send the alarm the command for arm night.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to arm night.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to arm night the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
alarm_trigger:
|
||||
description: Send the alarm the command for trigger.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: An optional code to trigger the alarm control panel with.
|
||||
example: 1234
|
||||
|
||||
envisalink_alarm_keypress:
|
||||
description: Send custom keypresses to the alarm.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of the alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
keypress:
|
||||
description: 'String to send to the alarm panel (1-6 characters).'
|
||||
example: '*71'
|
||||
|
||||
alarmdecoder_alarm_toggle_chime:
|
||||
description: Send the alarm the toggle chime command.
|
||||
fields:
|
||||
entity_id:
|
||||
description: Name of the alarm control panel to trigger.
|
||||
example: 'alarm_control_panel.downstairs'
|
||||
code:
|
||||
description: A required code to toggle the alarm control panel chime with.
|
||||
example: 1234
|
||||
|
||||
@@ -11,9 +11,9 @@ import voluptuous as vol
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, STATE_UNKNOWN, CONF_CODE, CONF_NAME,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.5']
|
||||
@@ -22,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'SimpliSafe'
|
||||
DOMAIN = 'simplisafe'
|
||||
|
||||
NOTIFICATION_ID = 'simplisafe_notification'
|
||||
NOTIFICATION_TITLE = 'SimpliSafe Setup'
|
||||
|
||||
@@ -65,7 +66,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation a SimpliSafe alarm."""
|
||||
"""Representation of a SimpliSafe alarm."""
|
||||
|
||||
def __init__(self, simplisafe, name, code):
|
||||
"""Initialize the SimpliSafe alarm."""
|
||||
@@ -82,7 +83,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters if code is defined."""
|
||||
"""Return one or more characters if code is defined."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
@property
|
||||
@@ -103,12 +104,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
'temperature': self.simplisafe.temperature(),
|
||||
'alarm': self.simplisafe.alarm(),
|
||||
'co': self.simplisafe.carbon_monoxide(),
|
||||
'fire': self.simplisafe.fire(),
|
||||
'alarm': self.simplisafe.alarm(),
|
||||
'flood': self.simplisafe.flood(),
|
||||
'last_event': self.simplisafe.last_event(),
|
||||
'flood': self.simplisafe.flood()
|
||||
'temperature': self.simplisafe.temperature(),
|
||||
}
|
||||
|
||||
def update(self):
|
||||
|
||||
@@ -9,26 +9,27 @@ import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.spc import (
|
||||
SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY)
|
||||
ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY, SpcWebGateway)
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPC_AREA_MODE_TO_STATE = {'0': STATE_ALARM_DISARMED,
|
||||
'1': STATE_ALARM_ARMED_HOME,
|
||||
'3': STATE_ALARM_ARMED_AWAY}
|
||||
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):
|
||||
"""Get the alarm state."""
|
||||
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the SPC alarm control panel platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_AREAS] is None):
|
||||
@@ -42,7 +43,7 @@ def async_setup_platform(hass, config, async_add_devices,
|
||||
|
||||
|
||||
class SpcAlarm(alarm.AlarmControlPanel):
|
||||
"""Represents the SPC alarm panel."""
|
||||
"""Representation of the SPC alarm panel."""
|
||||
|
||||
def __init__(self, api, area):
|
||||
"""Initialize the SPC alarm panel."""
|
||||
@@ -57,7 +58,7 @@ class SpcAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Calbback for init handlers."""
|
||||
"""Call for adding new entities."""
|
||||
self.hass.data[DATA_REGISTRY].register_alarm_device(
|
||||
self._area_id, self)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import logging
|
||||
from time import sleep
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.verisure import CONF_ALARM, CONF_CODE_DIGITS
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
from homeassistant.components.verisure import (CONF_ALARM, CONF_CODE_DIGITS)
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
@@ -43,7 +43,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Verisure alarm status."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initalize the Verisure alarm panel."""
|
||||
"""Initialize the Verisure alarm panel."""
|
||||
self._state = STATE_UNKNOWN
|
||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
||||
self._changed_by = None
|
||||
|
||||
@@ -8,11 +8,10 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.const import (STATE_UNKNOWN,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY)
|
||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
|
||||
STATE_UNKNOWN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -41,7 +40,7 @@ class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Callback when entity is added to hass."""
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
|
||||
|
||||
@property
|
||||
|
||||
@@ -34,7 +34,7 @@ DEFAULT_SKIP_FIRST = False
|
||||
|
||||
ALERT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_DONE_MESSAGE, default=None): cv.string,
|
||||
vol.Optional(CONF_DONE_MESSAGE): cv.string,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_STATE, default=STATE_ON): cv.string,
|
||||
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
|
||||
@@ -121,7 +121,7 @@ def async_setup(hass, config):
|
||||
# Setup alerts
|
||||
for entity_id, alert in alerts.items():
|
||||
entity = Alert(hass, entity_id,
|
||||
alert[CONF_NAME], alert[CONF_DONE_MESSAGE],
|
||||
alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE),
|
||||
alert[CONF_ENTITY_ID], alert[CONF_STATE],
|
||||
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
|
||||
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])
|
||||
@@ -277,7 +277,7 @@ class Alert(ToggleEntity):
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_toggle(self):
|
||||
def async_toggle(self, **kwargs):
|
||||
"""Async toggle alert."""
|
||||
if self._ack:
|
||||
return self.async_turn_on()
|
||||
|
||||
@@ -10,18 +10,30 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import entityfilter
|
||||
|
||||
from . import flash_briefings, intent, smart_home
|
||||
from .const import (
|
||||
DOMAIN, CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL)
|
||||
from . import flash_briefings, intent
|
||||
CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
|
||||
CONF_FILTER, CONF_ENTITY_CONFIG)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FLASH_BRIEFINGS = 'flash_briefings'
|
||||
CONF_SMART_HOME = 'smart_home'
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
CONF_FLASH_BRIEFINGS = 'flash_briefings'
|
||||
ALEXA_ENTITY_SCHEMA = vol.Schema({
|
||||
vol.Optional(smart_home.CONF_DESCRIPTION): cv.string,
|
||||
vol.Optional(smart_home.CONF_DISPLAY_CATEGORIES): cv.string,
|
||||
vol.Optional(smart_home.CONF_NAME): cv.string,
|
||||
})
|
||||
|
||||
SMART_HOME_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
|
||||
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
@@ -33,7 +45,10 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_TEXT, default=""): cv.template,
|
||||
vol.Optional(CONF_DISPLAY_URL): cv.template,
|
||||
}]),
|
||||
}
|
||||
},
|
||||
# vol.Optional here would mean we couldn't distinguish between an empty
|
||||
# smart_home: and none at all.
|
||||
CONF_SMART_HOME: vol.Any(SMART_HOME_SCHEMA, None),
|
||||
}
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -49,4 +64,12 @@ def async_setup(hass, config):
|
||||
if flash_briefings_config:
|
||||
flash_briefings.async_setup(hass, flash_briefings_config)
|
||||
|
||||
try:
|
||||
smart_home_config = config[CONF_SMART_HOME]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
smart_home_config = smart_home_config or SMART_HOME_SCHEMA({})
|
||||
smart_home.async_setup(hass, smart_home_config)
|
||||
|
||||
return True
|
||||
|
||||
@@ -8,6 +8,9 @@ CONF_AUDIO = 'audio'
|
||||
CONF_TEXT = 'text'
|
||||
CONF_DISPLAY_URL = 'display_url'
|
||||
|
||||
CONF_FILTER = 'filter'
|
||||
CONF_ENTITY_CONFIG = 'entity_config'
|
||||
|
||||
ATTR_UID = 'uid'
|
||||
ATTR_UPDATE_DATE = 'updateDate'
|
||||
ATTR_TITLE_TEXT = 'titleText'
|
||||
|
||||
@@ -5,19 +5,18 @@ For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import copy
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
from homeassistant.components import http
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.components import http
|
||||
|
||||
from .const import (
|
||||
CONF_UID, CONF_TITLE, CONF_AUDIO, CONF_TEXT, CONF_DISPLAY_URL, ATTR_UID,
|
||||
ATTR_UPDATE_DATE, ATTR_TITLE_TEXT, ATTR_STREAM_URL, ATTR_MAIN_TEXT,
|
||||
ATTR_REDIRECTION_URL, DATE_FORMAT)
|
||||
|
||||
ATTR_MAIN_TEXT, ATTR_REDIRECTION_URL, ATTR_STREAM_URL, ATTR_TITLE_TEXT,
|
||||
ATTR_UID, ATTR_UPDATE_DATE, CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT,
|
||||
CONF_TITLE, CONF_UID, DATE_FORMAT)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -46,11 +45,11 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
@callback
|
||||
def get(self, request, briefing_id):
|
||||
"""Handle Alexa Flash Briefing request."""
|
||||
_LOGGER.debug('Received Alexa flash briefing request for: %s',
|
||||
_LOGGER.debug("Received Alexa flash briefing request for: %s",
|
||||
briefing_id)
|
||||
|
||||
if self.flash_briefings.get(briefing_id) is None:
|
||||
err = 'No configured Alexa flash briefing was found for: %s'
|
||||
err = "No configured Alexa flash briefing was found for: %s"
|
||||
_LOGGER.error(err, briefing_id)
|
||||
return b'', 404
|
||||
|
||||
|
||||
@@ -3,30 +3,31 @@ 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
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.components import http
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import intent
|
||||
from homeassistant.util.decorator import Registry
|
||||
|
||||
from .const import DOMAIN, SYN_RESOLUTION_MATCH
|
||||
|
||||
INTENTS_API_ENDPOINT = '/api/alexa'
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HANDLERS = Registry()
|
||||
|
||||
INTENTS_API_ENDPOINT = '/api/alexa'
|
||||
|
||||
|
||||
class SpeechType(enum.Enum):
|
||||
"""The Alexa speech types."""
|
||||
|
||||
plaintext = "PlainText"
|
||||
ssml = "SSML"
|
||||
plaintext = 'PlainText'
|
||||
ssml = 'SSML'
|
||||
|
||||
|
||||
SPEECH_MAPPINGS = {
|
||||
@@ -38,8 +39,8 @@ SPEECH_MAPPINGS = {
|
||||
class CardType(enum.Enum):
|
||||
"""The Alexa card types."""
|
||||
|
||||
simple = "Simple"
|
||||
link_account = "LinkAccount"
|
||||
simple = 'Simple'
|
||||
link_account = 'LinkAccount'
|
||||
|
||||
|
||||
@callback
|
||||
@@ -64,7 +65,7 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
|
||||
_LOGGER.debug('Received Alexa request: %s', message)
|
||||
_LOGGER.debug("Received Alexa request: %s", message)
|
||||
|
||||
try:
|
||||
response = yield from async_handle_message(hass, message)
|
||||
@@ -81,7 +82,7 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
"This intent is not yet configured within Home Assistant."))
|
||||
|
||||
except intent.InvalidSlotInfo as err:
|
||||
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
|
||||
_LOGGER.error("Received invalid slot data from Alexa: %s", err)
|
||||
return self.json(intent_error_response(
|
||||
hass, message,
|
||||
"Invalid slot information received for this intent."))
|
||||
@@ -109,6 +110,7 @@ def async_handle_message(hass, message):
|
||||
- intent.UnknownIntent
|
||||
- intent.InvalidSlotInfo
|
||||
- intent.IntentError
|
||||
|
||||
"""
|
||||
req = message.get('request')
|
||||
req_type = req['type']
|
||||
@@ -138,6 +140,7 @@ def async_handle_intent(hass, message):
|
||||
- intent.UnknownIntent
|
||||
- intent.InvalidSlotInfo
|
||||
- intent.IntentError
|
||||
|
||||
"""
|
||||
req = message.get('request')
|
||||
alexa_intent_info = req.get('intent')
|
||||
|
||||
@@ -2,73 +2,573 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from homeassistant.components import (
|
||||
alert, automation, cover, fan, group, input_boolean, light, lock,
|
||||
media_player, scene, script, switch, http, sensor)
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_LOCK,
|
||||
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, CONF_NAME, SERVICE_LOCK,
|
||||
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET)
|
||||
from homeassistant.components import (
|
||||
alert, automation, cover, fan, group, input_boolean, light, lock,
|
||||
media_player, scene, script, switch)
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
|
||||
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
|
||||
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
|
||||
|
||||
HANDLERS = Registry()
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
API_DIRECTIVE = 'directive'
|
||||
API_ENDPOINT = 'endpoint'
|
||||
API_EVENT = 'event'
|
||||
API_CONTEXT = 'context'
|
||||
API_HEADER = 'header'
|
||||
API_PAYLOAD = 'payload'
|
||||
|
||||
API_TEMP_UNITS = {
|
||||
TEMP_FAHRENHEIT: 'FAHRENHEIT',
|
||||
TEMP_CELSIUS: 'CELSIUS',
|
||||
}
|
||||
|
||||
SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home'
|
||||
|
||||
CONF_DESCRIPTION = 'description'
|
||||
CONF_DISPLAY_CATEGORIES = 'display_categories'
|
||||
CONF_NAME = 'name'
|
||||
|
||||
HANDLERS = Registry()
|
||||
ENTITY_ADAPTERS = Registry()
|
||||
|
||||
|
||||
MAPPING_COMPONENT = {
|
||||
alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
automation.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
cover.DOMAIN: [
|
||||
'DOOR', ('Alexa.PowerController',), {
|
||||
cover.SUPPORT_SET_POSITION: 'Alexa.PercentageController',
|
||||
class _DisplayCategory(object):
|
||||
"""Possible display categories for Discovery response.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
|
||||
"""
|
||||
|
||||
# Describes a combination of devices set to a specific state, when the
|
||||
# state change must occur in a specific order. For example, a "watch
|
||||
# Netflix" scene might require the: 1. TV to be powered on & 2. Input set
|
||||
# to HDMI1. Applies to Scenes
|
||||
ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER"
|
||||
|
||||
# Indicates media devices with video or photo capabilities.
|
||||
CAMERA = "CAMERA"
|
||||
|
||||
# Indicates a door.
|
||||
DOOR = "DOOR"
|
||||
|
||||
# Indicates light sources or fixtures.
|
||||
LIGHT = "LIGHT"
|
||||
|
||||
# An endpoint that cannot be described in on of the other categories.
|
||||
OTHER = "OTHER"
|
||||
|
||||
# Describes a combination of devices set to a specific state, when the
|
||||
# order of the state change is not important. For example a bedtime scene
|
||||
# might include turning off lights and lowering the thermostat, but the
|
||||
# order is unimportant. Applies to Scenes
|
||||
SCENE_TRIGGER = "SCENE_TRIGGER"
|
||||
|
||||
# Indicates an endpoint that locks.
|
||||
SMARTLOCK = "SMARTLOCK"
|
||||
|
||||
# Indicates modules that are plugged into an existing electrical outlet.
|
||||
# Can control a variety of devices.
|
||||
SMARTPLUG = "SMARTPLUG"
|
||||
|
||||
# Indicates the endpoint is a speaker or speaker system.
|
||||
SPEAKER = "SPEAKER"
|
||||
|
||||
# Indicates in-wall switches wired to the electrical system. Can control a
|
||||
# variety of devices.
|
||||
SWITCH = "SWITCH"
|
||||
|
||||
# Indicates endpoints that report the temperature only.
|
||||
TEMPERATURE_SENSOR = "TEMPERATURE_SENSOR"
|
||||
|
||||
# Indicates endpoints that control temperature, stand-alone air
|
||||
# conditioners, or heaters with direct temperature control.
|
||||
THERMOSTAT = "THERMOSTAT"
|
||||
|
||||
# Indicates the endpoint is a television.
|
||||
# pylint: disable=invalid-name
|
||||
TV = "TV"
|
||||
|
||||
|
||||
def _capability(interface,
|
||||
version=3,
|
||||
supports_deactivation=None,
|
||||
retrievable=None,
|
||||
properties_supported=None,
|
||||
cap_type='AlexaInterface'):
|
||||
"""Return a Smart Home API capability object.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#capability-object
|
||||
|
||||
There are some additional fields allowed but not implemented here since
|
||||
we've no use case for them yet:
|
||||
|
||||
- proactively_reported
|
||||
|
||||
`supports_deactivation` applies only to scenes.
|
||||
"""
|
||||
result = {
|
||||
'type': cap_type,
|
||||
'interface': interface,
|
||||
'version': version,
|
||||
}
|
||||
|
||||
if supports_deactivation is not None:
|
||||
result['supportsDeactivation'] = supports_deactivation
|
||||
|
||||
if retrievable is not None:
|
||||
result['retrievable'] = retrievable
|
||||
|
||||
if properties_supported is not None:
|
||||
result['properties'] = {'supported': properties_supported}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _UnsupportedInterface(Exception):
|
||||
"""This entity does not support the requested Smart Home API interface."""
|
||||
|
||||
|
||||
class _UnsupportedProperty(Exception):
|
||||
"""This entity does not support the requested Smart Home API property."""
|
||||
|
||||
|
||||
class _AlexaEntity(object):
|
||||
"""An adaptation of an entity, expressed in Alexa's terms.
|
||||
|
||||
The API handlers should manipulate entities only through this interface.
|
||||
"""
|
||||
|
||||
def __init__(self, config, entity):
|
||||
self.config = config
|
||||
self.entity = entity
|
||||
self.entity_conf = config.entity_config.get(entity.entity_id, {})
|
||||
|
||||
def friendly_name(self):
|
||||
"""Return the Alexa API friendly name."""
|
||||
return self.entity_conf.get(CONF_NAME, self.entity.name)
|
||||
|
||||
def description(self):
|
||||
"""Return the Alexa API description."""
|
||||
return self.entity_conf.get(CONF_DESCRIPTION, self.entity.entity_id)
|
||||
|
||||
def entity_id(self):
|
||||
"""Return the Alexa API entity id."""
|
||||
return self.entity.entity_id.replace('.', '#')
|
||||
|
||||
def display_categories(self):
|
||||
"""Return a list of display categories."""
|
||||
entity_conf = self.config.entity_config.get(self.entity.entity_id, {})
|
||||
if CONF_DISPLAY_CATEGORIES in entity_conf:
|
||||
return [entity_conf[CONF_DISPLAY_CATEGORIES]]
|
||||
return self.default_display_categories()
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return a list of default display categories.
|
||||
|
||||
This can be overridden by the user in the Home Assistant configuration.
|
||||
|
||||
See also _DisplayCategory.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_interface(self, capability):
|
||||
"""Return the given _AlexaInterface.
|
||||
|
||||
Raises _UnsupportedInterface.
|
||||
"""
|
||||
pass
|
||||
|
||||
def interfaces(self):
|
||||
"""Return a list of supported interfaces.
|
||||
|
||||
Used for discovery. The list should contain _AlexaInterface instances.
|
||||
If the list is empty, this entity will not be discovered.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class _AlexaInterface(object):
|
||||
def __init__(self, entity):
|
||||
self.entity = entity
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def properties_supported():
|
||||
"""Return what properties this entity supports."""
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def properties_proactively_reported():
|
||||
"""Return True if properties asynchronously reported."""
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def properties_retrievable():
|
||||
"""Return True if properties can be retrieved."""
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_property(name):
|
||||
"""Read and return a property.
|
||||
|
||||
Return value should be a dict, or raise _UnsupportedProperty.
|
||||
|
||||
Properties can also have a timeOfSample and uncertaintyInMilliseconds,
|
||||
but returning those metadata is not yet implemented.
|
||||
"""
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
@staticmethod
|
||||
def supports_deactivation():
|
||||
"""Applicable only to scenes."""
|
||||
return None
|
||||
|
||||
def serialize_discovery(self):
|
||||
"""Serialize according to the Discovery API."""
|
||||
result = {
|
||||
'type': 'AlexaInterface',
|
||||
'interface': self.name(),
|
||||
'version': '3',
|
||||
'properties': {
|
||||
'supported': self.properties_supported(),
|
||||
'proactivelyReported': self.properties_proactively_reported(),
|
||||
'retrievable': self.properties_retrievable(),
|
||||
},
|
||||
}
|
||||
],
|
||||
fan.DOMAIN: [
|
||||
'OTHER', ('Alexa.PowerController',), {
|
||||
fan.SUPPORT_SET_SPEED: 'Alexa.PercentageController',
|
||||
|
||||
# pylint: disable=assignment-from-none
|
||||
supports_deactivation = self.supports_deactivation()
|
||||
if supports_deactivation is not None:
|
||||
result['supportsDeactivation'] = supports_deactivation
|
||||
return result
|
||||
|
||||
def serialize_properties(self):
|
||||
"""Return properties serialized for an API response."""
|
||||
for prop in self.properties_supported():
|
||||
prop_name = prop['name']
|
||||
yield {
|
||||
'name': prop_name,
|
||||
'namespace': self.name(),
|
||||
'value': self.get_property(prop_name),
|
||||
}
|
||||
|
||||
|
||||
class _AlexaPowerController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.PowerController'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'powerState'}]
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'powerState':
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
if self.entity.state == STATE_ON:
|
||||
return 'ON'
|
||||
return 'OFF'
|
||||
|
||||
|
||||
class _AlexaLockController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.LockController'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'lockState'}]
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'lockState':
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
if self.entity.state == STATE_LOCKED:
|
||||
return 'LOCKED'
|
||||
elif self.entity.state == STATE_UNLOCKED:
|
||||
return 'UNLOCKED'
|
||||
return 'JAMMED'
|
||||
|
||||
|
||||
class _AlexaSceneController(_AlexaInterface):
|
||||
def __init__(self, entity, supports_deactivation):
|
||||
_AlexaInterface.__init__(self, entity)
|
||||
self.supports_deactivation = lambda: supports_deactivation
|
||||
|
||||
def name(self):
|
||||
return 'Alexa.SceneController'
|
||||
|
||||
|
||||
class _AlexaBrightnessController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.BrightnessController'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'brightness'}]
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'brightness':
|
||||
raise _UnsupportedProperty(name)
|
||||
if 'brightness' in self.entity.attributes:
|
||||
return round(self.entity.attributes['brightness'] / 255.0 * 100)
|
||||
return 0
|
||||
|
||||
|
||||
class _AlexaColorController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.ColorController'
|
||||
|
||||
|
||||
class _AlexaColorTemperatureController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.ColorTemperatureController'
|
||||
|
||||
|
||||
class _AlexaPercentageController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.PercentageController'
|
||||
|
||||
|
||||
class _AlexaSpeaker(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.Speaker'
|
||||
|
||||
|
||||
class _AlexaStepSpeaker(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.StepSpeaker'
|
||||
|
||||
|
||||
class _AlexaPlaybackController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.PlaybackController'
|
||||
|
||||
|
||||
class _AlexaInputController(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.InputController'
|
||||
|
||||
|
||||
class _AlexaTemperatureSensor(_AlexaInterface):
|
||||
def name(self):
|
||||
return 'Alexa.TemperatureSensor'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'temperature'}]
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'temperature':
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
|
||||
return {
|
||||
'value': float(self.entity.state),
|
||||
'scale': API_TEMP_UNITS[unit],
|
||||
}
|
||||
],
|
||||
group.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
input_boolean.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
light.DOMAIN: [
|
||||
'LIGHT', ('Alexa.PowerController',), {
|
||||
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController',
|
||||
light.SUPPORT_RGB_COLOR: 'Alexa.ColorController',
|
||||
light.SUPPORT_XY_COLOR: 'Alexa.ColorController',
|
||||
light.SUPPORT_COLOR_TEMP: 'Alexa.ColorTemperatureController',
|
||||
}
|
||||
],
|
||||
lock.DOMAIN: ['SMARTLOCK', ('Alexa.LockController',), None],
|
||||
media_player.DOMAIN: [
|
||||
'TV', ('Alexa.PowerController',), {
|
||||
media_player.SUPPORT_VOLUME_SET: 'Alexa.Speaker',
|
||||
media_player.SUPPORT_PLAY: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_PAUSE: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_STOP: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_NEXT_TRACK: 'Alexa.PlaybackController',
|
||||
media_player.SUPPORT_PREVIOUS_TRACK: 'Alexa.PlaybackController',
|
||||
}
|
||||
],
|
||||
scene.DOMAIN: ['ACTIVITY_TRIGGER', ('Alexa.SceneController',), None],
|
||||
script.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
|
||||
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
|
||||
}
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(alert.DOMAIN)
|
||||
@ENTITY_ADAPTERS.register(automation.DOMAIN)
|
||||
@ENTITY_ADAPTERS.register(group.DOMAIN)
|
||||
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
|
||||
class _GenericCapabilities(_AlexaEntity):
|
||||
"""A generic, on/off device.
|
||||
|
||||
The choice of last resort.
|
||||
"""
|
||||
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.OTHER]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(switch.DOMAIN)
|
||||
class _SwitchCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.SWITCH]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(cover.DOMAIN)
|
||||
class _CoverCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.DOOR]
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & cover.SUPPORT_SET_POSITION:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(light.DOMAIN)
|
||||
class _LightCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.LIGHT]
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & light.SUPPORT_BRIGHTNESS:
|
||||
yield _AlexaBrightnessController(self.entity)
|
||||
if supported & light.SUPPORT_RGB_COLOR:
|
||||
yield _AlexaColorController(self.entity)
|
||||
if supported & light.SUPPORT_XY_COLOR:
|
||||
yield _AlexaColorController(self.entity)
|
||||
if supported & light.SUPPORT_COLOR_TEMP:
|
||||
yield _AlexaColorTemperatureController(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(fan.DOMAIN)
|
||||
class _FanCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.OTHER]
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(lock.DOMAIN)
|
||||
class _LockCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.SMARTLOCK]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaLockController(self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
|
||||
class _MediaPlayerCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.TV]
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & media_player.SUPPORT_VOLUME_SET:
|
||||
yield _AlexaSpeaker(self.entity)
|
||||
|
||||
step_volume_features = (media_player.SUPPORT_VOLUME_MUTE |
|
||||
media_player.SUPPORT_VOLUME_STEP)
|
||||
if supported & step_volume_features:
|
||||
yield _AlexaStepSpeaker(self.entity)
|
||||
|
||||
playback_features = (media_player.SUPPORT_PLAY |
|
||||
media_player.SUPPORT_PAUSE |
|
||||
media_player.SUPPORT_STOP |
|
||||
media_player.SUPPORT_NEXT_TRACK |
|
||||
media_player.SUPPORT_PREVIOUS_TRACK)
|
||||
if supported & playback_features:
|
||||
yield _AlexaPlaybackController(self.entity)
|
||||
|
||||
if supported & media_player.SUPPORT_SELECT_SOURCE:
|
||||
yield _AlexaInputController(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(scene.DOMAIN)
|
||||
class _SceneCapabilities(_AlexaEntity):
|
||||
def description(self):
|
||||
# Required description as per Amazon Scene docs
|
||||
scene_fmt = '{} (Scene connected via Home Assistant)'
|
||||
return scene_fmt.format(_AlexaEntity.description(self))
|
||||
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.SCENE_TRIGGER]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaSceneController(self.entity,
|
||||
supports_deactivation=False)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(script.DOMAIN)
|
||||
class _ScriptCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
return [_DisplayCategory.ACTIVITY_TRIGGER]
|
||||
|
||||
def interfaces(self):
|
||||
can_cancel = bool(self.entity.attributes.get('can_cancel'))
|
||||
return [_AlexaSceneController(self.entity,
|
||||
supports_deactivation=can_cancel)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(sensor.DOMAIN)
|
||||
class _SensorCapabilities(_AlexaEntity):
|
||||
def default_display_categories(self):
|
||||
# although there are other kinds of sensors, all but temperature
|
||||
# sensors are currently ignored.
|
||||
return [_DisplayCategory.TEMPERATURE_SENSOR]
|
||||
|
||||
def interfaces(self):
|
||||
attrs = self.entity.attributes
|
||||
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
|
||||
TEMP_FAHRENHEIT,
|
||||
TEMP_CELSIUS,
|
||||
):
|
||||
yield _AlexaTemperatureSensor(self.entity)
|
||||
|
||||
|
||||
class _Cause(object):
|
||||
"""Possible causes for property changes.
|
||||
|
||||
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object
|
||||
"""
|
||||
|
||||
# Indicates that the event was caused by a customer interaction with an
|
||||
# application. For example, a customer switches on a light, or locks a door
|
||||
# using the Alexa app or an app provided by a device vendor.
|
||||
APP_INTERACTION = 'APP_INTERACTION'
|
||||
|
||||
# Indicates that the event was caused by a physical interaction with an
|
||||
# endpoint. For example manually switching on a light or manually locking a
|
||||
# door lock
|
||||
PHYSICAL_INTERACTION = 'PHYSICAL_INTERACTION'
|
||||
|
||||
# Indicates that the event was caused by the periodic poll of an appliance,
|
||||
# which found a change in value. For example, you might poll a temperature
|
||||
# sensor every hour, and send the updated temperature to Alexa.
|
||||
PERIODIC_POLL = 'PERIODIC_POLL'
|
||||
|
||||
# Indicates that the event was caused by the application of a device rule.
|
||||
# For example, a customer configures a rule to switch on a light if a
|
||||
# motion sensor detects motion. In this case, Alexa receives an event from
|
||||
# the motion sensor, and another event from the light to indicate that its
|
||||
# state change was caused by the rule.
|
||||
RULE_TRIGGER = 'RULE_TRIGGER'
|
||||
|
||||
# Indicates that the event was caused by a voice interaction with Alexa.
|
||||
# For example a user speaking to their Echo device.
|
||||
VOICE_INTERACTION = 'VOICE_INTERACTION'
|
||||
|
||||
|
||||
class Config:
|
||||
@@ -80,6 +580,52 @@ class Config:
|
||||
self.entity_config = entity_config or {}
|
||||
|
||||
|
||||
@ha.callback
|
||||
def async_setup(hass, config):
|
||||
"""Activate Smart Home functionality of Alexa component.
|
||||
|
||||
This is optional, triggered by having a `smart_home:` sub-section in the
|
||||
alexa configuration.
|
||||
|
||||
Even if that's disabled, the functionality in this module may still be used
|
||||
by the cloud component which will call async_handle_message directly.
|
||||
"""
|
||||
smart_home_config = Config(
|
||||
should_expose=config[CONF_FILTER],
|
||||
entity_config=config.get(CONF_ENTITY_CONFIG),
|
||||
)
|
||||
hass.http.register_view(SmartHomeView(smart_home_config))
|
||||
|
||||
|
||||
class SmartHomeView(http.HomeAssistantView):
|
||||
"""Expose Smart Home v3 payload interface via HTTP POST."""
|
||||
|
||||
url = SMART_HOME_HTTP_ENDPOINT
|
||||
name = 'api:alexa:smart_home'
|
||||
|
||||
def __init__(self, smart_home_config):
|
||||
"""Initialize."""
|
||||
self.smart_home_config = smart_home_config
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
"""Handle Alexa Smart Home requests.
|
||||
|
||||
The Smart Home API requires the endpoint to be implemented in AWS
|
||||
Lambda, which will need to forward the requests to here and pass back
|
||||
the response.
|
||||
"""
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
|
||||
|
||||
response = yield from 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)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_message(hass, config, message):
|
||||
"""Handle incoming API messages."""
|
||||
@@ -100,7 +646,11 @@ def async_handle_message(hass, config, message):
|
||||
return (yield from funct_ref(hass, config, message))
|
||||
|
||||
|
||||
def api_message(request, name='Response', namespace='Alexa', payload=None):
|
||||
def api_message(request,
|
||||
name='Response',
|
||||
namespace='Alexa',
|
||||
payload=None,
|
||||
context=None):
|
||||
"""Create a API formatted response message.
|
||||
|
||||
Async friendly.
|
||||
@@ -119,7 +669,7 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
|
||||
}
|
||||
}
|
||||
|
||||
# If a correlation token exsits, add it to header / Need by Async requests
|
||||
# If a correlation token exists, add it to header / Need by Async requests
|
||||
token = request[API_HEADER].get('correlationToken')
|
||||
if token:
|
||||
response[API_EVENT][API_HEADER]['correlationToken'] = token
|
||||
@@ -128,6 +678,9 @@ def api_message(request, name='Response', namespace='Alexa', payload=None):
|
||||
if API_ENDPOINT in request:
|
||||
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
|
||||
|
||||
if context is not None:
|
||||
response[API_CONTEXT] = context
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -159,55 +712,26 @@ def async_api_discovery(hass, config, request):
|
||||
entity.entity_id)
|
||||
continue
|
||||
|
||||
class_data = MAPPING_COMPONENT.get(entity.domain)
|
||||
|
||||
if not class_data:
|
||||
if entity.domain not in ENTITY_ADAPTERS:
|
||||
continue
|
||||
|
||||
entity_conf = config.entity_config.get(entity.entity_id, {})
|
||||
|
||||
friendly_name = entity_conf.get(CONF_NAME, entity.name)
|
||||
description = entity_conf.get(CONF_DESCRIPTION, entity.entity_id)
|
||||
|
||||
# Required description as per Amazon Scene docs
|
||||
if entity.domain == scene.DOMAIN:
|
||||
scene_fmt = '{} (Scene connected via Home Assistant)'
|
||||
description = scene_fmt.format(description)
|
||||
|
||||
display_categories = entity_conf.get(CONF_DISPLAY_CATEGORIES,
|
||||
class_data[0])
|
||||
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
|
||||
|
||||
endpoint = {
|
||||
'displayCategories': [display_categories],
|
||||
'displayCategories': alexa_entity.display_categories(),
|
||||
'additionalApplianceDetails': {},
|
||||
'endpointId': entity.entity_id.replace('.', '#'),
|
||||
'friendlyName': friendly_name,
|
||||
'description': description,
|
||||
'endpointId': alexa_entity.entity_id(),
|
||||
'friendlyName': alexa_entity.friendly_name(),
|
||||
'description': alexa_entity.description(),
|
||||
'manufacturerName': 'Home Assistant',
|
||||
}
|
||||
actions = set()
|
||||
|
||||
# static actions
|
||||
if class_data[1]:
|
||||
actions |= set(class_data[1])
|
||||
endpoint['capabilities'] = [
|
||||
i.serialize_discovery() for i in alexa_entity.interfaces()]
|
||||
|
||||
# dynamic actions
|
||||
if class_data[2]:
|
||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
for feature, action_name in class_data[2].items():
|
||||
if feature & supported > 0:
|
||||
actions.add(action_name)
|
||||
|
||||
# Write action into capabilities
|
||||
capabilities = []
|
||||
for action in actions:
|
||||
capabilities.append({
|
||||
'type': 'AlexaInterface',
|
||||
'interface': action,
|
||||
'version': 3,
|
||||
})
|
||||
|
||||
endpoint['capabilities'] = capabilities
|
||||
if not endpoint['capabilities']:
|
||||
_LOGGER.debug("Not exposing %s because it has no capabilities",
|
||||
entity.entity_id)
|
||||
continue
|
||||
discovery_endpoints.append(endpoint)
|
||||
|
||||
return api_message(
|
||||
@@ -216,7 +740,7 @@ def async_api_discovery(hass, config, request):
|
||||
|
||||
|
||||
def extract_entity(funct):
|
||||
"""Decorator for extract entity object from request."""
|
||||
"""Decorate for extract entity object from request."""
|
||||
@asyncio.coroutine
|
||||
def async_api_entity_wrapper(hass, config, request):
|
||||
"""Process a turn on request."""
|
||||
@@ -293,7 +817,7 @@ def async_api_set_brightness(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_adjust_brightness(hass, config, request, entity):
|
||||
"""Process a adjust brightness request."""
|
||||
"""Process an adjust brightness request."""
|
||||
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
|
||||
|
||||
# read current state
|
||||
@@ -379,7 +903,7 @@ def async_api_decrease_color_temp(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_increase_color_temp(hass, config, request, entity):
|
||||
"""Process a increase color temperature request."""
|
||||
"""Process an increase color temperature request."""
|
||||
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
|
||||
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
|
||||
|
||||
@@ -396,12 +920,48 @@ def async_api_increase_color_temp(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_activate(hass, config, request, entity):
|
||||
"""Process a activate request."""
|
||||
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
|
||||
"""Process an activate request."""
|
||||
domain = entity.domain
|
||||
|
||||
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
|
||||
ATTR_ENTITY_ID: entity.entity_id
|
||||
}, blocking=False)
|
||||
|
||||
return api_message(request)
|
||||
payload = {
|
||||
'cause': {'type': _Cause.VOICE_INTERACTION},
|
||||
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
|
||||
}
|
||||
|
||||
return api_message(
|
||||
request,
|
||||
name='ActivationStarted',
|
||||
namespace='Alexa.SceneController',
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.SceneController', 'Deactivate'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_deactivate(hass, config, request, entity):
|
||||
"""Process a deactivate request."""
|
||||
domain = entity.domain
|
||||
|
||||
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
|
||||
ATTR_ENTITY_ID: entity.entity_id
|
||||
}, blocking=False)
|
||||
|
||||
payload = {
|
||||
'cause': {'type': _Cause.VOICE_INTERACTION},
|
||||
'timestamp': '%sZ' % (datetime.utcnow().isoformat(),)
|
||||
}
|
||||
|
||||
return api_message(
|
||||
request,
|
||||
name='DeactivationStarted',
|
||||
namespace='Alexa.SceneController',
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
|
||||
@@ -439,7 +999,7 @@ def async_api_set_percentage(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_adjust_percentage(hass, config, request, entity):
|
||||
"""Process a adjust percentage request."""
|
||||
"""Process an adjust percentage request."""
|
||||
percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
|
||||
service = None
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
@@ -492,7 +1052,16 @@ def async_api_lock(hass, config, request, entity):
|
||||
ATTR_ENTITY_ID: entity.entity_id
|
||||
}, blocking=False)
|
||||
|
||||
return api_message(request)
|
||||
# Alexa expects a lockState in the response, we don't know the actual
|
||||
# lockState at this point but assume it is locked. It is reported
|
||||
# correctly later when ReportState is called. The alt. to this approach
|
||||
# is to implement DeferredResponse
|
||||
properties = [{
|
||||
'name': 'lockState',
|
||||
'namespace': 'Alexa.LockController',
|
||||
'value': 'LOCKED'
|
||||
}]
|
||||
return api_message(request, context={'properties': properties})
|
||||
|
||||
|
||||
# Not supported by Alexa yet
|
||||
@@ -500,7 +1069,7 @@ def async_api_lock(hass, config, request, entity):
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_unlock(hass, config, request, entity):
|
||||
"""Process a unlock request."""
|
||||
"""Process an unlock request."""
|
||||
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
|
||||
ATTR_ENTITY_ID: entity.entity_id
|
||||
}, blocking=False)
|
||||
@@ -527,11 +1096,46 @@ def async_api_set_volume(hass, config, request, entity):
|
||||
return api_message(request)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.InputController', 'SelectInput'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_select_input(hass, config, request, entity):
|
||||
"""Process a set input request."""
|
||||
media_input = request[API_PAYLOAD]['input']
|
||||
|
||||
# attempt to map the ALL UPPERCASE payload name to a source
|
||||
source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or []
|
||||
for source in source_list:
|
||||
# response will always be space separated, so format the source in the
|
||||
# most likely way to find a match
|
||||
formatted_source = source.lower().replace('-', ' ').replace('_', ' ')
|
||||
if formatted_source in media_input.lower():
|
||||
media_input = source
|
||||
break
|
||||
else:
|
||||
msg = 'failed to map input {} to a media source on {}'.format(
|
||||
media_input, entity.entity_id)
|
||||
_LOGGER.error(msg)
|
||||
return api_error(
|
||||
request, error_type='INVALID_VALUE', error_message=msg)
|
||||
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.ATTR_INPUT_SOURCE: media_input,
|
||||
}
|
||||
|
||||
yield from hass.services.async_call(
|
||||
entity.domain, media_player.SERVICE_SELECT_SOURCE,
|
||||
data, blocking=False)
|
||||
|
||||
return api_message(request)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_adjust_volume(hass, config, request, entity):
|
||||
"""Process a adjust volume request."""
|
||||
"""Process an adjust volume request."""
|
||||
volume_delta = int(request[API_PAYLOAD]['volume'])
|
||||
|
||||
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
|
||||
@@ -556,6 +1160,34 @@ def async_api_adjust_volume(hass, config, request, entity):
|
||||
return api_message(request)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_adjust_volume_step(hass, config, request, entity):
|
||||
"""Process an adjust volume step request."""
|
||||
# media_player volume up/down service does not support specifying steps
|
||||
# each component handles it differently e.g. via config.
|
||||
# For now we use the volumeSteps returned to figure out if we
|
||||
# should step up/down
|
||||
volume_step = request[API_PAYLOAD]['volumeSteps']
|
||||
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
}
|
||||
|
||||
if volume_step > 0:
|
||||
yield from hass.services.async_call(
|
||||
entity.domain, media_player.SERVICE_VOLUME_UP,
|
||||
data, blocking=False)
|
||||
elif volume_step < 0:
|
||||
yield from hass.services.async_call(
|
||||
entity.domain, media_player.SERVICE_VOLUME_DOWN,
|
||||
data, blocking=False)
|
||||
|
||||
return api_message(request)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute'))
|
||||
@HANDLERS.register(('Alexa.Speaker', 'SetMute'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
@@ -653,3 +1285,20 @@ def async_api_previous(hass, config, request, entity):
|
||||
data, blocking=False)
|
||||
|
||||
return api_message(request)
|
||||
|
||||
|
||||
@HANDLERS.register(('Alexa', 'ReportState'))
|
||||
@extract_entity
|
||||
@asyncio.coroutine
|
||||
def async_api_reportstate(hass, config, request, entity):
|
||||
"""Process a ReportState request."""
|
||||
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
|
||||
properties = []
|
||||
for interface in alexa_entity.interfaces():
|
||||
properties.extend(interface.serialize_properties())
|
||||
|
||||
return api_message(
|
||||
request,
|
||||
name='StateReport',
|
||||
context={'properties': properties}
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||
cv.time_period,
|
||||
vol.Optional(CONF_SENSORS, default=None):
|
||||
vol.Optional(CONF_SENSORS):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -140,11 +140,11 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
cv.time_period,
|
||||
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
|
||||
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
|
||||
vol.Optional(CONF_SWITCHES, default=None):
|
||||
vol.Optional(CONF_SWITCHES):
|
||||
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
|
||||
vol.Optional(CONF_SENSORS, default=None):
|
||||
vol.Optional(CONF_SENSORS):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
|
||||
vol.Optional(CONF_MOTION_SENSOR, default=None): cv.boolean,
|
||||
vol.Optional(CONF_MOTION_SENSOR): cv.boolean,
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -165,9 +165,9 @@ def async_setup(hass, config):
|
||||
password = cam_config.get(CONF_PASSWORD)
|
||||
name = cam_config[CONF_NAME]
|
||||
interval = cam_config[CONF_SCAN_INTERVAL]
|
||||
switches = cam_config[CONF_SWITCHES]
|
||||
sensors = cam_config[CONF_SENSORS]
|
||||
motion = cam_config[CONF_MOTION_SENSOR]
|
||||
switches = cam_config.get(CONF_SWITCHES)
|
||||
sensors = cam_config.get(CONF_SENSORS)
|
||||
motion = cam_config.get(CONF_MOTION_SENSOR)
|
||||
|
||||
# Init ip webcam
|
||||
cam = PyDroidIPCam(
|
||||
@@ -251,7 +251,7 @@ class AndroidIPCamEntity(Entity):
|
||||
"""The Android device running IP Webcam."""
|
||||
|
||||
def __init__(self, host, ipcam):
|
||||
"""Initialize the data oject."""
|
||||
"""Initialize the data object."""
|
||||
self._host = host
|
||||
self._ipcam = ipcam
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class APCUPSdData(object):
|
||||
"""
|
||||
|
||||
def __init__(self, host, port):
|
||||
"""Initialize the data oject."""
|
||||
"""Initialize the data object."""
|
||||
from apcaccess import status
|
||||
self._host = host
|
||||
self._port = port
|
||||
|
||||
@@ -131,8 +131,7 @@ class APIEventStream(HomeAssistantView):
|
||||
msg = "data: {}\n\n".format(payload)
|
||||
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
|
||||
msg.strip())
|
||||
response.write(msg.encode("UTF-8"))
|
||||
yield from response.drain()
|
||||
yield from response.write(msg.encode("UTF-8"))
|
||||
except asyncio.TimeoutError:
|
||||
yield from to_write.put(STREAM_PING_PAYLOAD)
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@ https://home-assistant.io/components/apple_tv/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from typing import Sequence, TypeVar, Union
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from typing import Union, TypeVar, Sequence
|
||||
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.components.discovery import SERVICE_APPLE_TV
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, CONF_NAME
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyatv==0.3.9']
|
||||
@@ -59,9 +60,9 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_LOGIN_ID): cv.string,
|
||||
vol.Optional(CONF_CREDENTIALS): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
|
||||
vol.Optional(CONF_START_OFF, default=False): cv.boolean
|
||||
vol.Optional(CONF_START_OFF, default=False): cv.boolean,
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -140,7 +141,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_service_handler(service):
|
||||
"""Handler for service calls."""
|
||||
"""Handle service calls."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
if service.service == SERVICE_SCAN:
|
||||
@@ -167,7 +168,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def atv_discovered(service, info):
|
||||
"""Setup an Apple TV that was auto discovered."""
|
||||
"""Set up an Apple TV that was auto discovered."""
|
||||
yield from _setup_atv(hass, {
|
||||
CONF_NAME: info['name'],
|
||||
CONF_HOST: info['host'],
|
||||
@@ -194,7 +195,7 @@ def async_setup(hass, config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def _setup_atv(hass, atv_config):
|
||||
"""Setup an Apple TV."""
|
||||
"""Set up an Apple TV."""
|
||||
import pyatv
|
||||
name = atv_config.get(CONF_NAME)
|
||||
host = atv_config.get(CONF_HOST)
|
||||
@@ -245,7 +246,7 @@ class AppleTVPowerManager:
|
||||
|
||||
@property
|
||||
def turned_on(self):
|
||||
"""If device is on or off."""
|
||||
"""Return true if device is on or off."""
|
||||
return self._is_on
|
||||
|
||||
def set_power_on(self, value):
|
||||
|
||||
@@ -47,7 +47,7 @@ def setup(hass, config):
|
||||
return False
|
||||
hass.data[DATA_ARLO] = arlo
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
|
||||
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
|
||||
hass.components.persistent_notification.create(
|
||||
'Error: {}<br />'
|
||||
'You will need to restart hass after fixing.'
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
"""Support for Asterisk Voicemail interface."""
|
||||
"""
|
||||
Support for Asterisk Voicemail interface.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/asterisk_mbox/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.const import (CONF_HOST,
|
||||
CONF_PORT, CONF_PASSWORD)
|
||||
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
|
||||
async_dispatcher_send)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect, async_dispatcher_send)
|
||||
|
||||
REQUIREMENTS = ['asterisk_mbox==0.4.0']
|
||||
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'asterisk_mbox'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request'
|
||||
SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@@ -43,7 +43,7 @@ def setup(hass, config):
|
||||
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
|
||||
|
||||
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config)
|
||||
discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
@@ -68,15 +68,14 @@ class AsteriskData(object):
|
||||
from asterisk_mbox.commands import CMD_MESSAGE_LIST
|
||||
|
||||
if command == CMD_MESSAGE_LIST:
|
||||
_LOGGER.info("AsteriskVM sent updated message list")
|
||||
self.messages = sorted(msg,
|
||||
key=lambda item: item['info']['origtime'],
|
||||
reverse=True)
|
||||
async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE,
|
||||
self.messages)
|
||||
_LOGGER.debug("AsteriskVM sent updated message list")
|
||||
self.messages = sorted(
|
||||
msg, key=lambda item: item['info']['origtime'], reverse=True)
|
||||
async_dispatcher_send(
|
||||
self.hass, SIGNAL_MESSAGE_UPDATE, self.messages)
|
||||
|
||||
@callback
|
||||
def _request_messages(self):
|
||||
"""Handle changes to the mailbox."""
|
||||
_LOGGER.info("Requesting message list")
|
||||
_LOGGER.debug("Requesting message list")
|
||||
self.client.messages()
|
||||
|
||||
257
homeassistant/components/august.py
Normal file
257
homeassistant/components/august.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
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
|
||||
|
||||
import voluptuous as vol
|
||||
from requests import RequestException
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT)
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_CONFIGURING = {}
|
||||
|
||||
REQUIREMENTS = ['py-august==0.4.0']
|
||||
|
||||
DEFAULT_TIMEOUT = 10
|
||||
ACTIVITY_FETCH_LIMIT = 10
|
||||
ACTIVITY_INITIAL_FETCH_LIMIT = 20
|
||||
|
||||
CONF_LOGIN_METHOD = 'login_method'
|
||||
CONF_INSTALL_ID = 'install_id'
|
||||
|
||||
NOTIFICATION_ID = 'august_notification'
|
||||
NOTIFICATION_TITLE = "August Setup"
|
||||
|
||||
AUGUST_CONFIG_FILE = '.august.conf'
|
||||
|
||||
DATA_AUGUST = 'august'
|
||||
DOMAIN = 'august'
|
||||
DEFAULT_ENTITY_NAMESPACE = 'august'
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=5)
|
||||
LOGIN_METHODS = ['phone', 'email']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS),
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_INSTALL_ID): cv.string,
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
AUGUST_COMPONENTS = [
|
||||
'camera', 'binary_sensor', 'lock'
|
||||
]
|
||||
|
||||
|
||||
def request_configuration(hass, config, api, authenticator):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = hass.components.configurator
|
||||
|
||||
def august_configuration_callback(data):
|
||||
"""Run when the configuration callback is called."""
|
||||
from august.authenticator import ValidationResult
|
||||
|
||||
result = authenticator.validate_verification_code(
|
||||
data.get('verification_code'))
|
||||
|
||||
if result == ValidationResult.INVALID_VERIFICATION_CODE:
|
||||
configurator.notify_errors(_CONFIGURING[DOMAIN],
|
||||
"Invalid verification code")
|
||||
elif result == ValidationResult.VALIDATED:
|
||||
setup_august(hass, config, api, authenticator)
|
||||
|
||||
if DOMAIN not in _CONFIGURING:
|
||||
authenticator.send_verification_code()
|
||||
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
login_method = conf.get(CONF_LOGIN_METHOD)
|
||||
|
||||
_CONFIGURING[DOMAIN] = configurator.request_config(
|
||||
NOTIFICATION_TITLE,
|
||||
august_configuration_callback,
|
||||
description="Please check your {} ({}) and enter the verification "
|
||||
"code below".format(login_method, username),
|
||||
submit_caption='Verify',
|
||||
fields=[{
|
||||
'id': 'verification_code',
|
||||
'name': "Verification code",
|
||||
'type': 'string'}]
|
||||
)
|
||||
|
||||
|
||||
def setup_august(hass, config, api, authenticator):
|
||||
"""Set up the August component."""
|
||||
from august.authenticator import AuthenticationState
|
||||
|
||||
authentication = None
|
||||
try:
|
||||
authentication = authenticator.authenticate()
|
||||
except RequestException as ex:
|
||||
_LOGGER.error("Unable to connect to August service: %s", str(ex))
|
||||
|
||||
hass.components.persistent_notification.create(
|
||||
"Error: {}<br />"
|
||||
"You will need to restart hass after fixing."
|
||||
"".format(ex),
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
|
||||
state = authentication.state
|
||||
|
||||
if state == AuthenticationState.AUTHENTICATED:
|
||||
if DOMAIN in _CONFIGURING:
|
||||
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
|
||||
|
||||
hass.data[DATA_AUGUST] = AugustData(api, authentication.access_token)
|
||||
|
||||
for component in AUGUST_COMPONENTS:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
elif state == AuthenticationState.BAD_PASSWORD:
|
||||
return False
|
||||
elif state == AuthenticationState.REQUIRES_VALIDATION:
|
||||
request_configuration(hass, config, api, authenticator)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the August component."""
|
||||
from august.api import Api
|
||||
from august.authenticator import Authenticator
|
||||
|
||||
conf = config[DOMAIN]
|
||||
api = Api(timeout=conf.get(CONF_TIMEOUT))
|
||||
|
||||
authenticator = Authenticator(
|
||||
api,
|
||||
conf.get(CONF_LOGIN_METHOD),
|
||||
conf.get(CONF_USERNAME),
|
||||
conf.get(CONF_PASSWORD),
|
||||
install_id=conf.get(CONF_INSTALL_ID),
|
||||
access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE))
|
||||
|
||||
return setup_august(hass, config, api, authenticator)
|
||||
|
||||
|
||||
class AugustData:
|
||||
"""August data object."""
|
||||
|
||||
def __init__(self, api, access_token):
|
||||
"""Init August data object."""
|
||||
self._api = api
|
||||
self._access_token = access_token
|
||||
self._doorbells = self._api.get_doorbells(self._access_token) or []
|
||||
self._locks = self._api.get_operable_locks(self._access_token) or []
|
||||
self._house_ids = [d.house_id for d in self._doorbells + self._locks]
|
||||
|
||||
self._doorbell_detail_by_id = {}
|
||||
self._lock_status_by_id = {}
|
||||
self._lock_detail_by_id = {}
|
||||
self._activities_by_id = {}
|
||||
|
||||
@property
|
||||
def house_ids(self):
|
||||
"""Return a list of house_ids."""
|
||||
return self._house_ids
|
||||
|
||||
@property
|
||||
def doorbells(self):
|
||||
"""Return a list of doorbells."""
|
||||
return self._doorbells
|
||||
|
||||
@property
|
||||
def locks(self):
|
||||
"""Return a list of locks."""
|
||||
return self._locks
|
||||
|
||||
def get_device_activities(self, device_id, *activity_types):
|
||||
"""Return a list of activities."""
|
||||
self._update_device_activities()
|
||||
|
||||
activities = self._activities_by_id.get(device_id, [])
|
||||
if activity_types:
|
||||
return [a for a in activities if a.activity_type in activity_types]
|
||||
return activities
|
||||
|
||||
def get_latest_device_activity(self, device_id, *activity_types):
|
||||
"""Return latest activity."""
|
||||
activities = self.get_device_activities(device_id, *activity_types)
|
||||
return next(iter(activities or []), None)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
|
||||
"""Update data object with latest from August API."""
|
||||
for house_id in self.house_ids:
|
||||
activities = self._api.get_house_activities(self._access_token,
|
||||
house_id,
|
||||
limit=limit)
|
||||
|
||||
device_ids = {a.device_id for a in activities}
|
||||
for device_id in device_ids:
|
||||
self._activities_by_id[device_id] = [a for a in activities if
|
||||
a.device_id == device_id]
|
||||
|
||||
def get_doorbell_detail(self, doorbell_id):
|
||||
"""Return doorbell detail."""
|
||||
self._update_doorbells()
|
||||
return self._doorbell_detail_by_id.get(doorbell_id)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_doorbells(self):
|
||||
detail_by_id = {}
|
||||
|
||||
for doorbell in self._doorbells:
|
||||
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
|
||||
self._access_token, doorbell.device_id)
|
||||
|
||||
self._doorbell_detail_by_id = detail_by_id
|
||||
|
||||
def get_lock_status(self, lock_id):
|
||||
"""Return lock status."""
|
||||
self._update_locks()
|
||||
return self._lock_status_by_id.get(lock_id)
|
||||
|
||||
def get_lock_detail(self, lock_id):
|
||||
"""Return lock detail."""
|
||||
self._update_locks()
|
||||
return self._lock_detail_by_id.get(lock_id)
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def _update_locks(self):
|
||||
status_by_id = {}
|
||||
detail_by_id = {}
|
||||
|
||||
for lock in self._locks:
|
||||
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)
|
||||
|
||||
self._lock_status_by_id = status_by_id
|
||||
self._lock_detail_by_id = detail_by_id
|
||||
|
||||
def lock(self, device_id):
|
||||
"""Lock the device."""
|
||||
return self._api.lock(self._access_token, device_id)
|
||||
|
||||
def unlock(self, device_id):
|
||||
"""Unlock the device."""
|
||||
return self._api.unlock(self._access_token, device_id)
|
||||
@@ -338,10 +338,9 @@ class AutomationEntity(ToggleEntity):
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_remove(self):
|
||||
"""Remove automation from HASS."""
|
||||
def async_will_remove_from_hass(self):
|
||||
"""Remove listeners when removing automation from HASS."""
|
||||
yield from self.async_turn_off()
|
||||
yield from super().async_remove()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_enable(self):
|
||||
|
||||
@@ -4,24 +4,21 @@ Support for Axis devices.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/axis/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.discovery import SERVICE_AXIS
|
||||
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
|
||||
CONF_EVENT, CONF_HOST, CONF_INCLUDE,
|
||||
CONF_NAME, CONF_PASSWORD, CONF_PORT,
|
||||
CONF_TRIGGER_TIME, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.const import (
|
||||
ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE,
|
||||
CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
|
||||
REQUIREMENTS = ['axis==14']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -81,10 +78,10 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
configurator = hass.components.configurator
|
||||
|
||||
def configuration_callback(callback_data):
|
||||
"""Called when config is submitted."""
|
||||
"""Call when configuration is submitted."""
|
||||
if CONF_INCLUDE not in callback_data:
|
||||
configurator.notify_errors(request_id,
|
||||
"Functionality mandatory.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Functionality mandatory.")
|
||||
return False
|
||||
|
||||
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
|
||||
@@ -96,18 +93,20 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
try:
|
||||
device_config = DEVICE_SCHEMA(callback_data)
|
||||
except vol.Invalid:
|
||||
configurator.notify_errors(request_id,
|
||||
"Bad input, please check spelling.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Bad input, please check spelling.")
|
||||
return False
|
||||
|
||||
if setup_device(hass, config, device_config):
|
||||
del device_config['events']
|
||||
del device_config['signal']
|
||||
config_file = load_json(hass.config.path(CONFIG_FILE))
|
||||
config_file[serialnumber] = dict(device_config)
|
||||
save_json(hass.config.path(CONFIG_FILE), config_file)
|
||||
configurator.request_done(request_id)
|
||||
else:
|
||||
configurator.notify_errors(request_id,
|
||||
"Failed to register, please try again.")
|
||||
configurator.notify_errors(
|
||||
request_id, "Failed to register, please try again.")
|
||||
return False
|
||||
|
||||
title = '{} ({})'.format(name, host)
|
||||
@@ -145,7 +144,7 @@ def request_configuration(hass, config, name, host, serialnumber):
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Common setup for Axis devices."""
|
||||
"""Set up for Axis devices."""
|
||||
def _shutdown(call): # pylint: disable=unused-argument
|
||||
"""Stop the event stream on shutdown."""
|
||||
for serialnumber, device in AXIS_DEVICES.items():
|
||||
@@ -155,7 +154,7 @@ def setup(hass, config):
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||
|
||||
def axis_device_discovered(service, discovery_info):
|
||||
"""Called when axis devices has been found."""
|
||||
"""Call when axis devices has been found."""
|
||||
host = discovery_info[CONF_HOST]
|
||||
name = discovery_info['hostname']
|
||||
serialnumber = discovery_info['properties']['macaddress']
|
||||
@@ -171,8 +170,8 @@ def setup(hass, config):
|
||||
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
|
||||
return False
|
||||
if not setup_device(hass, config, device_config):
|
||||
_LOGGER.error("Couldn\'t set up %s",
|
||||
device_config[CONF_NAME])
|
||||
_LOGGER.error(
|
||||
"Couldn't set up %s", device_config[CONF_NAME])
|
||||
else:
|
||||
# New device, create configuration request for UI
|
||||
request_configuration(hass, config, name, host, serialnumber)
|
||||
@@ -191,7 +190,7 @@ def setup(hass, config):
|
||||
if CONF_NAME not in device_config:
|
||||
device_config[CONF_NAME] = device
|
||||
if not setup_device(hass, config, device_config):
|
||||
_LOGGER.error("Couldn\'t set up %s", device_config[CONF_NAME])
|
||||
_LOGGER.error("Couldn't set up %s", device_config[CONF_NAME])
|
||||
|
||||
def vapix_service(call):
|
||||
"""Service to send a message."""
|
||||
@@ -203,23 +202,21 @@ def setup(hass, config):
|
||||
call.data[SERVICE_PARAM])
|
||||
hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response)
|
||||
return True
|
||||
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME])
|
||||
_LOGGER.info("Couldn't find device %s", call.data[CONF_NAME])
|
||||
return False
|
||||
|
||||
# Register service with Home Assistant.
|
||||
hass.services.register(DOMAIN,
|
||||
SERVICE_VAPIX_CALL,
|
||||
vapix_service,
|
||||
schema=SERVICE_SCHEMA)
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA)
|
||||
return True
|
||||
|
||||
|
||||
def setup_device(hass, config, device_config):
|
||||
"""Set up device."""
|
||||
"""Set up an Axis device."""
|
||||
from axis import AxisDevice
|
||||
|
||||
def signal_callback(action, event):
|
||||
"""Callback to configure events when initialized on event stream."""
|
||||
"""Call to configure events when initialized on event stream."""
|
||||
if action == 'add':
|
||||
event_config = {
|
||||
CONF_EVENT: event,
|
||||
@@ -228,11 +225,8 @@ def setup_device(hass, config, device_config):
|
||||
CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME]
|
||||
}
|
||||
component = event.event_platform
|
||||
discovery.load_platform(hass,
|
||||
component,
|
||||
DOMAIN,
|
||||
event_config,
|
||||
config)
|
||||
discovery.load_platform(
|
||||
hass, component, DOMAIN, event_config, config)
|
||||
|
||||
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE],
|
||||
EVENT_TYPES))
|
||||
@@ -243,7 +237,7 @@ def setup_device(hass, config, device_config):
|
||||
|
||||
if device.serial_number is None:
|
||||
# If there is no serial number a connection could not be made
|
||||
_LOGGER.error("Couldn\'t connect to %s", device_config[CONF_HOST])
|
||||
_LOGGER.error("Couldn't connect to %s", device_config[CONF_HOST])
|
||||
return False
|
||||
|
||||
for component in device_config[CONF_INCLUDE]:
|
||||
@@ -255,11 +249,8 @@ def setup_device(hass, config, device_config):
|
||||
CONF_USERNAME: device_config[CONF_USERNAME],
|
||||
CONF_PASSWORD: device_config[CONF_PASSWORD]
|
||||
}
|
||||
discovery.load_platform(hass,
|
||||
component,
|
||||
DOMAIN,
|
||||
camera_config,
|
||||
config)
|
||||
discovery.load_platform(
|
||||
hass, component, DOMAIN, camera_config, config)
|
||||
|
||||
AXIS_DEVICES[device.serial_number] = device
|
||||
if event_types:
|
||||
@@ -273,9 +264,9 @@ class AxisDeviceEvent(Entity):
|
||||
def __init__(self, event_config):
|
||||
"""Initialize the event."""
|
||||
self.axis_event = event_config[CONF_EVENT]
|
||||
self._name = '{}_{}_{}'.format(event_config[CONF_NAME],
|
||||
self.axis_event.event_type,
|
||||
self.axis_event.id)
|
||||
self._name = '{}_{}_{}'.format(
|
||||
event_config[CONF_NAME], self.axis_event.event_type,
|
||||
self.axis_event.id)
|
||||
self.location = event_config[ATTR_LOCATION]
|
||||
self.axis_event.callback = self._update_callback
|
||||
|
||||
@@ -296,7 +287,7 @@ class AxisDeviceEvent(Entity):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
"""Return the polling state. No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
||||
@@ -4,7 +4,7 @@ Component to interface with binary sensors.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@@ -28,6 +28,7 @@ DEVICE_CLASSES = [
|
||||
'gas', # On means gas detected, Off means no gas (clear)
|
||||
'heat', # On means hot, Off means normal
|
||||
'light', # On means light detected, Off means no light
|
||||
'lock', # On means open (unlocked), Off means closed (locked)
|
||||
'moisture', # On means wet, Off means dry
|
||||
'motion', # On means motion detected, Off means no motion (clear)
|
||||
'moving', # On means moving, Off means not moving (stopped)
|
||||
@@ -47,13 +48,12 @@ DEVICE_CLASSES = [
|
||||
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Track states and offer events for binary sensors."""
|
||||
component = EntityComponent(
|
||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
||||
|
||||
yield from component.async_setup(config)
|
||||
await component.async_setup(config)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -3,23 +3,22 @@ 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
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice, \
|
||||
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA
|
||||
from homeassistant.components.ads import DATA_ADS, CONF_ADS_VAR
|
||||
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['ads']
|
||||
DEFAULT_NAME = 'ADS binary sensor'
|
||||
|
||||
DEPENDENCIES = ['ads']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
@@ -44,7 +43,7 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
"""Representation of ADS binary sensors."""
|
||||
|
||||
def __init__(self, ads_hub, name, ads_var, device_class):
|
||||
"""Initialize AdsBinarySensor entity."""
|
||||
"""Initialize ADS binary sensor."""
|
||||
self._name = name
|
||||
self._state = False
|
||||
self._device_class = device_class or 'moving'
|
||||
@@ -56,15 +55,13 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
"""Register device notification."""
|
||||
def update(name, value):
|
||||
"""Handle device notifications."""
|
||||
_LOGGER.debug('Variable %s changed its value to %d',
|
||||
name, value)
|
||||
_LOGGER.debug('Variable %s changed its value to %d', name, value)
|
||||
self._state = value
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.hass.async_add_job(
|
||||
self._ads_hub.add_device_notification,
|
||||
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update
|
||||
)
|
||||
self.ads_var, self._ads_hub.PLCTYPE_BOOL, update)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
||||
97
homeassistant/components/binary_sensor/august.py
Normal file
97
homeassistant/components/binary_sensor/august.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Support for August binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.august/
|
||||
"""
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from homeassistant.components.august import DATA_AUGUST
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
|
||||
DEPENDENCIES = ['august']
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
def _retrieve_online_state(data, doorbell):
|
||||
"""Get the latest state of the sensor."""
|
||||
detail = data.get_doorbell_detail(doorbell.device_id)
|
||||
return detail.is_online
|
||||
|
||||
|
||||
def _retrieve_motion_state(data, doorbell):
|
||||
from august.activity import ActivityType
|
||||
return _activity_time_based_state(data, doorbell,
|
||||
[ActivityType.DOORBELL_MOTION,
|
||||
ActivityType.DOORBELL_DING])
|
||||
|
||||
|
||||
def _retrieve_ding_state(data, doorbell):
|
||||
from august.activity import ActivityType
|
||||
return _activity_time_based_state(data, doorbell,
|
||||
[ActivityType.DOORBELL_DING])
|
||||
|
||||
|
||||
def _activity_time_based_state(data, doorbell, activity_types):
|
||||
"""Get the latest state of the sensor."""
|
||||
latest = data.get_latest_device_activity(doorbell.device_id,
|
||||
*activity_types)
|
||||
|
||||
if latest is not None:
|
||||
start = latest.activity_start_time
|
||||
end = latest.activity_end_time + timedelta(seconds=30)
|
||||
return start <= datetime.now() <= end
|
||||
return None
|
||||
|
||||
|
||||
# Sensor types: Name, device_class, state_provider
|
||||
SENSOR_TYPES = {
|
||||
'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state],
|
||||
'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state],
|
||||
'doorbell_online': ['Online', 'connectivity', _retrieve_online_state],
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the August binary sensors."""
|
||||
data = hass.data[DATA_AUGUST]
|
||||
devices = []
|
||||
|
||||
for doorbell in data.doorbells:
|
||||
for sensor_type in SENSOR_TYPES:
|
||||
devices.append(AugustBinarySensor(data, sensor_type, doorbell))
|
||||
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class AugustBinarySensor(BinarySensorDevice):
|
||||
"""Representation of an August binary sensor."""
|
||||
|
||||
def __init__(self, data, sensor_type, doorbell):
|
||||
"""Initialize the sensor."""
|
||||
self._data = data
|
||||
self._sensor_type = sensor_type
|
||||
self._doorbell = doorbell
|
||||
self._state = None
|
||||
|
||||
@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[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])
|
||||
|
||||
def update(self):
|
||||
"""Get the latest state of the sensor."""
|
||||
state_provider = SENSOR_TYPES[self._sensor_type][2]
|
||||
self._state = state_provider(self._data, self._doorbell)
|
||||
@@ -4,13 +4,12 @@ Support for Axis binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.axis/
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
from homeassistant.components.axis import (AxisDeviceEvent)
|
||||
from homeassistant.const import (CONF_TRIGGER_TIME)
|
||||
from homeassistant.components.axis import AxisDeviceEvent
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_TRIGGER_TIME
|
||||
from homeassistant.helpers.event import track_point_in_utc_time
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
@@ -20,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Axis device event."""
|
||||
"""Set up the Axis binary devices."""
|
||||
add_devices([AxisBinarySensor(hass, discovery_info)], True)
|
||||
|
||||
|
||||
@@ -28,7 +27,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
|
||||
"""Representation of a binary Axis event."""
|
||||
|
||||
def __init__(self, hass, event_config):
|
||||
"""Initialize the binary sensor."""
|
||||
"""Initialize the Axis binary sensor."""
|
||||
self.hass = hass
|
||||
self._state = False
|
||||
self._delay = event_config[CONF_TRIGGER_TIME]
|
||||
@@ -56,7 +55,7 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
|
||||
# Set timer to wait until updating the state
|
||||
def _delay_update(now):
|
||||
"""Timer callback for sensor update."""
|
||||
_LOGGER.debug("%s Called delayed (%s sec) update.",
|
||||
_LOGGER.debug("%s called delayed (%s sec) update",
|
||||
self._name, self._delay)
|
||||
self.schedule_update_ha_state()
|
||||
self._timer = None
|
||||
|
||||
@@ -24,7 +24,7 @@ SENSOR_TYPES = {
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
@@ -50,7 +50,6 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
self._device_id = device['DeviceID']
|
||||
self._sensor_name = sensor_name
|
||||
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
|
||||
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
@@ -58,11 +57,6 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
"""Return the name of the BloomSky device and this sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID for this sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
|
||||
@@ -27,7 +27,7 @@ DEFAULT_NAME = 'Alarm'
|
||||
DEFAULT_PORT = '5007'
|
||||
DEFAULT_SSL = False
|
||||
|
||||
SCAN_INTERVAL = datetime.timedelta(seconds=1)
|
||||
SCAN_INTERVAL = datetime.timedelta(seconds=10)
|
||||
|
||||
ZONE_TYPES_SCHEMA = vol.Schema({
|
||||
cv.positive_int: vol.In(DEVICE_CLASSES),
|
||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
sensors = []
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Initializing Client")
|
||||
_LOGGER.debug("Initializing client")
|
||||
client = concord232_client.Client('http://{}:{}'.format(host, port))
|
||||
client.zones = client.list_zones()
|
||||
client.last_zone_update = datetime.datetime.now()
|
||||
|
||||
@@ -4,11 +4,11 @@ Support for deCONZ binary sensor.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.deconz/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.deconz import DOMAIN as DECONZ_DATA
|
||||
from homeassistant.components.deconz import (
|
||||
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import callback
|
||||
|
||||
@@ -17,16 +17,17 @@ DEPENDENCIES = ['deconz']
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup binary sensor for deCONZ component."""
|
||||
"""Set up the deCONZ binary sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
from pydeconz.sensor import DECONZ_BINARY_SENSOR
|
||||
sensors = hass.data[DECONZ_DATA].sensors
|
||||
sensors = hass.data[DATA_DECONZ].sensors
|
||||
entities = []
|
||||
|
||||
for sensor in sensors.values():
|
||||
if sensor.type in DECONZ_BINARY_SENSOR:
|
||||
for key in sorted(sensors.keys(), key=int):
|
||||
sensor = sensors[key]
|
||||
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
|
||||
entities.append(DeconzBinarySensor(sensor))
|
||||
async_add_devices(entities, True)
|
||||
|
||||
@@ -35,13 +36,14 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a binary sensor."""
|
||||
|
||||
def __init__(self, sensor):
|
||||
"""Setup sensor and add update callback to get data from websocket."""
|
||||
"""Set up sensor and add update callback to get data from websocket."""
|
||||
self._sensor = sensor
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Subscribe sensors events."""
|
||||
self._sensor.register_async_callback(self.async_update_callback)
|
||||
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
@@ -65,9 +67,14 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
"""Return the name of the sensor."""
|
||||
return self._sensor.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this sensor."""
|
||||
return self._sensor.uniqueid
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Class of the sensor."""
|
||||
"""Return the class of the sensor."""
|
||||
return self._sensor.sensor_class
|
||||
|
||||
@property
|
||||
@@ -92,6 +99,6 @@ class DeconzBinarySensor(BinarySensorDevice):
|
||||
attr = {
|
||||
ATTR_BATTERY_LEVEL: self._sensor.battery,
|
||||
}
|
||||
if self._sensor.type == PRESENCE:
|
||||
if self._sensor.type in PRESENCE:
|
||||
attr['dark'] = self._sensor.dark
|
||||
return attr
|
||||
|
||||
@@ -50,11 +50,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
|
||||
"""Return the status of the sensor."""
|
||||
return self._state == 'true'
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID of this sensor."""
|
||||
return "binary_sensor_ecobee_{}_{}".format(self._name, self.index)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
|
||||
78
homeassistant/components/binary_sensor/egardia.py
Normal file
78
homeassistant/components/binary_sensor/egardia.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
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
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from homeassistant.components.egardia import (
|
||||
EGARDIA_DEVICE, ATTR_DISCOVER_DEVICES)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
|
||||
'Door Contact': 'opening',
|
||||
'IR': 'motion'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
return
|
||||
|
||||
disc_info = discovery_info[ATTR_DISCOVER_DEVICES]
|
||||
# multiple devices here!
|
||||
async_add_devices(
|
||||
(
|
||||
EgardiaBinarySensor(
|
||||
sensor_id=disc_info[sensor]['id'],
|
||||
name=disc_info[sensor]['name'],
|
||||
egardia_system=hass.data[EGARDIA_DEVICE],
|
||||
device_class=EGARDIA_TYPE_TO_DEVICE_CLASS.get(
|
||||
disc_info[sensor]['type'], None)
|
||||
)
|
||||
for sensor in disc_info
|
||||
), True)
|
||||
|
||||
|
||||
class EgardiaBinarySensor(BinarySensorDevice):
|
||||
"""Represents a sensor based on an Egardia sensor (IR, Door Contact)."""
|
||||
|
||||
def __init__(self, sensor_id, name, egardia_system, device_class):
|
||||
"""Initialize the sensor device."""
|
||||
self._id = sensor_id
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._device_class = device_class
|
||||
self._egardia_system = egardia_system
|
||||
|
||||
def update(self):
|
||||
"""Update the status."""
|
||||
egardia_input = self._egardia_system.getsensorstate(self._id)
|
||||
self._state = STATE_ON if egardia_input else STATE_OFF
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Whether the device is switched on."""
|
||||
return self._state == STATE_ON
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""Whether the device is hidden by default."""
|
||||
# these type of sensors are probably mainly used for automations
|
||||
return True
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""The device class."""
|
||||
return self._device_class
|
||||
@@ -50,7 +50,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
|
||||
self._zone_type = zone_type
|
||||
self._zone_number = zone_number
|
||||
|
||||
_LOGGER.debug('Setting up zone: ' + zone_name)
|
||||
_LOGGER.debug('Setting up zone: %s', zone_name)
|
||||
super().__init__(zone_name, info, controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
|
||||
@@ -48,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the FFmpeg binary moition sensor."""
|
||||
"""Set up the FFmpeg binary motion sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
|
||||
@@ -238,6 +238,5 @@ class FlicButton(BinarySensorDevice):
|
||||
import pyflic
|
||||
|
||||
if connection_status == pyflic.ConnectionStatus.Disconnected:
|
||||
_LOGGER.info("Button (%s) disconnected. Reason: %s",
|
||||
self.address, disconnect_reason)
|
||||
self.remove()
|
||||
_LOGGER.warning("Button (%s) disconnected. Reason: %s",
|
||||
self.address, disconnect_reason)
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.const import (
|
||||
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
|
||||
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
|
||||
|
||||
REQUIREMENTS = ['pyhik==0.1.4']
|
||||
REQUIREMENTS = ['pyhik==0.1.8']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_IGNORED = 'ignored'
|
||||
@@ -48,6 +48,9 @@ DEVICE_CLASS_MAP = {
|
||||
'Face Detection': 'motion',
|
||||
'Scene Change Detection': 'motion',
|
||||
'I/O': None,
|
||||
'Unattended Baggage': 'motion',
|
||||
'Attended Baggage': 'motion',
|
||||
'Recording Failure': None,
|
||||
}
|
||||
|
||||
CUSTOMIZE_SCHEMA = vol.Schema({
|
||||
@@ -56,7 +59,7 @@ CUSTOMIZE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=None): cv.string,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_SSL, default=False): cv.boolean,
|
||||
@@ -118,7 +121,7 @@ class HikvisionData(object):
|
||||
"""Hikvision device event stream object."""
|
||||
|
||||
def __init__(self, hass, url, port, name, username, password):
|
||||
"""Initialize the data oject."""
|
||||
"""Initialize the data object."""
|
||||
from pyhik.hikvision import HikCamera
|
||||
self._url = url
|
||||
self._port = port
|
||||
@@ -211,8 +214,8 @@ class HikvisionBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return an unique ID."""
|
||||
return '{}.{}'.format(self.__class__, self._id)
|
||||
"""Return a unique ID."""
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
|
||||
'contactsensor': 'opening'}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveBinarySensorEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
"""Representation of a Hive binary sensor."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the hive sensor."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.node_device_type = hivedevice["Hive_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return self.node_name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.session.sensor.get_state(self.node_id,
|
||||
self.node_device_type)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data frome Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
"""
|
||||
Support for the Hive devices.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
|
||||
'contactsensor': 'opening'}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Hive sensor devices."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
session = hass.data.get(DATA_HIVE)
|
||||
|
||||
add_devices([HiveBinarySensorEntity(session, discovery_info)])
|
||||
|
||||
|
||||
class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
"""Representation of a Hive binary sensor."""
|
||||
|
||||
def __init__(self, hivesession, hivedevice):
|
||||
"""Initialize the hive sensor."""
|
||||
self.node_id = hivedevice["Hive_NodeID"]
|
||||
self.node_name = hivedevice["Hive_NodeName"]
|
||||
self.device_type = hivedevice["HA_DeviceType"]
|
||||
self.node_device_type = hivedevice["Hive_DeviceType"]
|
||||
self.session = hivesession
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the binary sensor."""
|
||||
return self.node_name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self.session.sensor.get_state(self.node_id,
|
||||
self.node_device_type)
|
||||
|
||||
def update(self):
|
||||
"""Update all Node data from Hive."""
|
||||
self.session.core.update_data(self.node_id)
|
||||
|
||||
96
homeassistant/components/binary_sensor/ihc.py
Normal file
96
homeassistant/components/binary_sensor/ihc.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""IHC binary sensor platform.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ihc/
|
||||
"""
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.components.ihc import (
|
||||
validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO)
|
||||
from homeassistant.components.ihc.const import CONF_INVERTING
|
||||
from homeassistant.components.ihc.ihcdevice import IHCDevice
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['ihc']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_BINARY_SENSORS, default=[]):
|
||||
vol.All(cv.ensure_list, [
|
||||
vol.All({
|
||||
vol.Required(CONF_ID): cv.positive_int,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
|
||||
}, validate_name)
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the IHC binary sensor platform."""
|
||||
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
|
||||
info = hass.data[IHC_DATA][IHC_INFO]
|
||||
devices = []
|
||||
if discovery_info:
|
||||
for name, device in discovery_info.items():
|
||||
ihc_id = device['ihc_id']
|
||||
product_cfg = device['product_cfg']
|
||||
product = device['product']
|
||||
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
|
||||
product_cfg.get(CONF_TYPE),
|
||||
product_cfg[CONF_INVERTING],
|
||||
product)
|
||||
devices.append(sensor)
|
||||
else:
|
||||
binary_sensors = config[CONF_BINARY_SENSORS]
|
||||
for sensor_cfg in binary_sensors:
|
||||
ihc_id = sensor_cfg[CONF_ID]
|
||||
name = sensor_cfg[CONF_NAME]
|
||||
sensor_type = sensor_cfg.get(CONF_TYPE)
|
||||
inverting = sensor_cfg[CONF_INVERTING]
|
||||
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
|
||||
sensor_type, inverting)
|
||||
devices.append(sensor)
|
||||
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
class IHCBinarySensor(IHCDevice, BinarySensorDevice):
|
||||
"""IHC Binary Sensor.
|
||||
|
||||
The associated IHC resource can be any in or output from a IHC product
|
||||
or function block, but it must be a boolean ON/OFF resources.
|
||||
"""
|
||||
|
||||
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
|
||||
sensor_type: str, inverting: bool,
|
||||
product: Element = None) -> None:
|
||||
"""Initialize the IHC binary sensor."""
|
||||
super().__init__(ihc_controller, name, ihc_id, info, product)
|
||||
self._state = None
|
||||
self._sensor_type = sensor_type
|
||||
self.inverting = inverting
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._sensor_type
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on/open."""
|
||||
return self._state
|
||||
|
||||
def on_ihc_change(self, ihc_id, value):
|
||||
"""IHC resource has changed."""
|
||||
if self.inverting:
|
||||
self._state = not value
|
||||
else:
|
||||
self._state = value
|
||||
self.schedule_update_ha_state()
|
||||
@@ -2,86 +2,56 @@
|
||||
Support for INSTEON dimmers via PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon_plm/
|
||||
https://home-assistant.io/components/binary_sensor.insteon_plm/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.components.insteon_plm import InsteonPLMEntity
|
||||
|
||||
DEPENDENCIES = ['insteon_plm']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSOR_TYPES = {'openClosedSensor': 'opening',
|
||||
'motionSensor': 'motion',
|
||||
'doorSensor': 'door',
|
||||
'leakSensor': 'moisture'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the INSTEON PLM device class for the hass platform."""
|
||||
plm = hass.data['insteon_plm']
|
||||
|
||||
device_list = []
|
||||
for device in discovery_info:
|
||||
name = device.get('address')
|
||||
address = device.get('address_hex')
|
||||
address = discovery_info['address']
|
||||
device = plm.devices[address]
|
||||
state_key = discovery_info['state_key']
|
||||
|
||||
_LOGGER.info('Registered %s with binary_sensor platform.', name)
|
||||
_LOGGER.debug('Adding device %s entity %s to Binary Sensor platform',
|
||||
device.address.hex, device.states[state_key].name)
|
||||
|
||||
device_list.append(
|
||||
InsteonPLMBinarySensorDevice(hass, plm, address, name)
|
||||
)
|
||||
new_entity = InsteonPLMBinarySensor(device, state_key)
|
||||
|
||||
async_add_devices(device_list)
|
||||
async_add_devices([new_entity])
|
||||
|
||||
|
||||
class InsteonPLMBinarySensorDevice(BinarySensorDevice):
|
||||
"""A Class for an Insteon device."""
|
||||
class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
|
||||
"""A Class for an Insteon device entity."""
|
||||
|
||||
def __init__(self, hass, plm, address, name):
|
||||
"""Initialize the binarysensor."""
|
||||
self._hass = hass
|
||||
self._plm = plm.protocol
|
||||
self._address = address
|
||||
self._name = name
|
||||
|
||||
self._plm.add_update_callback(
|
||||
self.async_binarysensor_update, {'address': self._address})
|
||||
def __init__(self, device, state_key):
|
||||
"""Initialize the INSTEON PLM binary sensor."""
|
||||
super().__init__(device, state_key)
|
||||
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""Return the address of the node."""
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the node."""
|
||||
return self._name
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._sensor_type
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
sensorstate = self._plm.get_device_attr(self._address, 'sensorstate')
|
||||
_LOGGER.info("Sensor state for %s is %s", self._address, sensorstate)
|
||||
sensorstate = self._insteon_device_state.value
|
||||
return bool(sensorstate)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Provide attributes for display on device card."""
|
||||
insteon_plm = get_component('insteon_plm')
|
||||
return insteon_plm.common_attributes(self)
|
||||
|
||||
def get_attr(self, key):
|
||||
"""Return specified attribute for this device."""
|
||||
return self._plm.get_device_attr(self.address, key)
|
||||
|
||||
@callback
|
||||
def async_binarysensor_update(self, message):
|
||||
"""Receive notification from transport that new data exists."""
|
||||
_LOGGER.info("Received update calback from PLM for %s", self._address)
|
||||
self._hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@@ -56,24 +56,17 @@ def setup_platform(hass, config: ConfigType,
|
||||
else:
|
||||
device_type = _detect_device_type(node)
|
||||
subnode_id = int(node.nid[-1])
|
||||
if device_type == 'opening':
|
||||
# Door/window sensors use an optional "negative" node
|
||||
if subnode_id == 4:
|
||||
if (device_type == 'opening' or device_type == 'moisture'):
|
||||
# These sensors use an optional "negative" subnode 2 to snag
|
||||
# all state changes
|
||||
if subnode_id == 2:
|
||||
parent_device.add_negative_node(node)
|
||||
elif subnode_id == 4:
|
||||
# Subnode 4 is the heartbeat node, which we will represent
|
||||
# as a separate binary_sensor
|
||||
device = ISYBinarySensorHeartbeat(node, parent_device)
|
||||
parent_device.add_heartbeat_device(device)
|
||||
devices.append(device)
|
||||
elif subnode_id == 2:
|
||||
parent_device.add_negative_node(node)
|
||||
elif device_type == 'moisture':
|
||||
# Moisure nodes have a subnode 2, but we ignore it because it's
|
||||
# just the inverse of the primary node.
|
||||
if subnode_id == 4:
|
||||
# Heartbeat node
|
||||
device = ISYBinarySensorHeartbeat(node, parent_device)
|
||||
parent_device.add_heartbeat_device(device)
|
||||
devices.append(device)
|
||||
else:
|
||||
# We don't yet have any special logic for other sensor types,
|
||||
# so add the nodes as individual devices
|
||||
|
||||
@@ -4,13 +4,13 @@ Support for KNX/IP binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.knx/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES, \
|
||||
KNXAutomation
|
||||
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, \
|
||||
BinarySensorDevice
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.knx import (
|
||||
ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation)
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@@ -25,6 +25,7 @@ CONF_DEFAULT_HOOK = 'on'
|
||||
CONF_COUNTER = 'counter'
|
||||
CONF_DEFAULT_COUNTER = 1
|
||||
CONF_ACTION = 'action'
|
||||
CONF_RESET_AFTER = 'reset_after'
|
||||
|
||||
CONF__ACTION = 'turn_off_action'
|
||||
|
||||
@@ -34,7 +35,7 @@ DEPENDENCIES = ['knx']
|
||||
AUTOMATION_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
|
||||
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
|
||||
vol.Required(CONF_ACTION, default=None): cv.SCRIPT_SCHEMA
|
||||
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA
|
||||
})
|
||||
|
||||
AUTOMATIONS_SCHEMA = vol.All(
|
||||
@@ -48,25 +49,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.string,
|
||||
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
|
||||
cv.positive_int,
|
||||
vol.Optional(CONF_AUTOMATION, default=None): AUTOMATIONS_SCHEMA,
|
||||
vol.Optional(CONF_RESET_AFTER): cv.positive_int,
|
||||
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Set up binary sensor(s) for KNX platform."""
|
||||
if DATA_KNX not in hass.data \
|
||||
or not hass.data[DATA_KNX].initialized:
|
||||
return False
|
||||
|
||||
if discovery_info is not None:
|
||||
async_add_devices_discovery(hass, discovery_info, async_add_devices)
|
||||
else:
|
||||
async_add_devices_config(hass, config, async_add_devices)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@callback
|
||||
def async_add_devices_discovery(hass, discovery_info, async_add_devices):
|
||||
@@ -80,7 +75,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
|
||||
|
||||
@callback
|
||||
def async_add_devices_config(hass, config, async_add_devices):
|
||||
"""Set up binary senor for KNX platform configured within plattform."""
|
||||
"""Set up binary senor for KNX platform configured within platform."""
|
||||
name = config.get(CONF_NAME)
|
||||
import xknx
|
||||
binary_sensor = xknx.devices.BinarySensor(
|
||||
@@ -88,7 +83,8 @@ def async_add_devices_config(hass, config, async_add_devices):
|
||||
name=name,
|
||||
group_address=config.get(CONF_ADDRESS),
|
||||
device_class=config.get(CONF_DEVICE_CLASS),
|
||||
significant_bit=config.get(CONF_SIGNIFICANT_BIT))
|
||||
significant_bit=config.get(CONF_SIGNIFICANT_BIT),
|
||||
reset_after=config.get(CONF_RESET_AFTER))
|
||||
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
|
||||
|
||||
entity = KNXBinarySensor(hass, binary_sensor)
|
||||
@@ -108,7 +104,7 @@ class KNXBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a KNX binary sensor."""
|
||||
|
||||
def __init__(self, hass, device):
|
||||
"""Initialization of KNXBinarySensor."""
|
||||
"""Initialize of KNX binary sensor."""
|
||||
self.device = device
|
||||
self.hass = hass
|
||||
self.async_register_callbacks()
|
||||
@@ -117,11 +113,10 @@ class KNXBinarySensor(BinarySensorDevice):
|
||||
@callback
|
||||
def async_register_callbacks(self):
|
||||
"""Register callbacks to update hass after device was changed."""
|
||||
@asyncio.coroutine
|
||||
def after_update_callback(device):
|
||||
"""Callback after device was updated."""
|
||||
async def after_update_callback(device):
|
||||
"""Call after device was updated."""
|
||||
# pylint: disable=unused-argument
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
self.device.register_device_updated_cb(after_update_callback)
|
||||
|
||||
@property
|
||||
|
||||
@@ -36,7 +36,7 @@ class MaxCubeShutter(BinarySensorDevice):
|
||||
def __init__(self, hass, name, rf_address):
|
||||
"""Initialize MAX! Cube BinarySensorDevice."""
|
||||
self._name = name
|
||||
self._sensor_type = 'opening'
|
||||
self._sensor_type = 'window'
|
||||
self._rf_address = rf_address
|
||||
self._cubehandle = hass.data[MAXCUBE_HANDLE]
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
97
homeassistant/components/binary_sensor/mercedesme.py
Normal file
97
homeassistant/components/binary_sensor/mercedesme.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Support for Mercedes cars with Mercedes ME.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mercedesme/
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
from homeassistant.components.mercedesme import (
|
||||
DATA_MME, FEATURE_NOT_AVAILABLE, MercedesMeEntity, BINARY_SENSORS)
|
||||
|
||||
DEPENDENCIES = ['mercedesme']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the sensor platform."""
|
||||
data = hass.data[DATA_MME].data
|
||||
|
||||
if not data.cars:
|
||||
_LOGGER.error("No cars found. Check component log.")
|
||||
return
|
||||
|
||||
devices = []
|
||||
for car in data.cars:
|
||||
for key, value in sorted(BINARY_SENSORS.items()):
|
||||
if car['availabilities'].get(key, 'INVALID') == 'VALID':
|
||||
devices.append(MercedesMEBinarySensor(
|
||||
data, key, value[0], car["vin"], None))
|
||||
else:
|
||||
_LOGGER.warning(FEATURE_NOT_AVAILABLE, key, car["license"])
|
||||
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class MercedesMEBinarySensor(MercedesMeEntity, BinarySensorDevice):
|
||||
"""Representation of a Sensor."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the state of the binary sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
if self._internal_name == "windowsClosed":
|
||||
return {
|
||||
"window_front_left": self._car["windowStatusFrontLeft"],
|
||||
"window_front_right": self._car["windowStatusFrontRight"],
|
||||
"window_rear_left": self._car["windowStatusRearLeft"],
|
||||
"window_rear_right": self._car["windowStatusRearRight"],
|
||||
"original_value": self._car[self._internal_name],
|
||||
"last_update": datetime.datetime.fromtimestamp(
|
||||
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"car": self._car["license"]
|
||||
}
|
||||
elif self._internal_name == "tireWarningLight":
|
||||
return {
|
||||
"front_right_tire_pressure_kpa":
|
||||
self._car["frontRightTirePressureKpa"],
|
||||
"front_left_tire_pressure_kpa":
|
||||
self._car["frontLeftTirePressureKpa"],
|
||||
"rear_right_tire_pressure_kpa":
|
||||
self._car["rearRightTirePressureKpa"],
|
||||
"rear_left_tire_pressure_kpa":
|
||||
self._car["rearLeftTirePressureKpa"],
|
||||
"original_value": self._car[self._internal_name],
|
||||
"last_update": datetime.datetime.fromtimestamp(
|
||||
self._car["lastUpdate"]
|
||||
).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"car": self._car["license"],
|
||||
}
|
||||
return {
|
||||
"original_value": self._car[self._internal_name],
|
||||
"last_update": datetime.datetime.fromtimestamp(
|
||||
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
"car": self._car["license"]
|
||||
}
|
||||
|
||||
def update(self):
|
||||
"""Fetch new state data for the sensor."""
|
||||
self._car = next(
|
||||
car for car in self._data.cars if car["vin"] == self._vin)
|
||||
|
||||
if self._internal_name == "windowsClosed":
|
||||
self._state = bool(self._car[self._internal_name] == "CLOSED")
|
||||
elif self._internal_name == "tireWarningLight":
|
||||
self._state = bool(self._car[self._internal_name] != "INACTIVE")
|
||||
else:
|
||||
self._state = self._car[self._internal_name] is True
|
||||
|
||||
_LOGGER.debug("Updated %s Value: %s IsOn: %s",
|
||||
self._internal_name, self._state, self.is_on)
|
||||
@@ -14,8 +14,8 @@ import homeassistant.components.mqtt as mqtt
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
|
||||
CONF_DEVICE_CLASS)
|
||||
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
|
||||
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
|
||||
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
|
||||
@@ -24,8 +24,10 @@ import homeassistant.helpers.config_validation as cv
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'MQTT Binary sensor'
|
||||
|
||||
DEFAULT_PAYLOAD_OFF = 'OFF'
|
||||
DEFAULT_PAYLOAD_ON = 'ON'
|
||||
DEFAULT_FORCE_UPDATE = False
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
@@ -34,6 +36,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@@ -53,6 +56,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
config.get(CONF_AVAILABILITY_TOPIC),
|
||||
config.get(CONF_DEVICE_CLASS),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_FORCE_UPDATE),
|
||||
config.get(CONF_PAYLOAD_ON),
|
||||
config.get(CONF_PAYLOAD_OFF),
|
||||
config.get(CONF_PAYLOAD_AVAILABLE),
|
||||
@@ -65,7 +69,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
"""Representation a binary sensor that is updated by MQTT."""
|
||||
|
||||
def __init__(self, name, state_topic, availability_topic, device_class,
|
||||
qos, payload_on, payload_off, payload_available,
|
||||
qos, force_update, payload_on, payload_off, payload_available,
|
||||
payload_not_available, value_template):
|
||||
"""Initialize the MQTT binary sensor."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
@@ -77,6 +81,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
self._payload_on = payload_on
|
||||
self._payload_off = payload_off
|
||||
self._qos = qos
|
||||
self._force_update = force_update
|
||||
self._template = value_template
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -94,6 +99,11 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
self._state = True
|
||||
elif payload == self._payload_off:
|
||||
self._state = False
|
||||
else: # Payload is not for this entity
|
||||
_LOGGER.warning('No matching payload found'
|
||||
' for entity: %s with state_topic: %s',
|
||||
self._name, self._state_topic)
|
||||
return
|
||||
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@@ -119,3 +129,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def force_update(self):
|
||||
"""Force update."""
|
||||
return self._force_update
|
||||
|
||||
85
homeassistant/components/binary_sensor/mychevy.py
Normal file
85
homeassistant/components/binary_sensor/mychevy.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Support for MyChevy sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mychevy/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.mychevy import (
|
||||
EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC
|
||||
)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
ENTITY_ID_FORMAT, BinarySensorDevice)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util import slugify
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SENSORS = [
|
||||
EVBinarySensorConfig("Plugged In", "plugged_in", "plug")
|
||||
]
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the MyChevy sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
sensors = []
|
||||
hub = hass.data[MYCHEVY_DOMAIN]
|
||||
for sconfig in SENSORS:
|
||||
sensors.append(EVBinarySensor(hub, sconfig))
|
||||
|
||||
async_add_devices(sensors)
|
||||
|
||||
|
||||
class EVBinarySensor(BinarySensorDevice):
|
||||
"""Base EVSensor class.
|
||||
|
||||
The only real difference between sensors is which units and what
|
||||
attribute from the car object they are returning. All logic can be
|
||||
built with just setting subclass attributes.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, connection, config):
|
||||
"""Initialize sensor with car connection."""
|
||||
self._conn = connection
|
||||
self._name = config.name
|
||||
self._attr = config.attr
|
||||
self._type = config.device_class
|
||||
self._is_on = None
|
||||
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(
|
||||
'{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name)))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return if on."""
|
||||
return self._is_on
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
UPDATE_TOPIC, self.async_update_callback)
|
||||
|
||||
@callback
|
||||
def async_update_callback(self):
|
||||
"""Update state."""
|
||||
if self._conn.car is not None:
|
||||
self._is_on = getattr(self._conn.car, self._attr, None)
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
@@ -5,21 +5,20 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mysensors/
|
||||
"""
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
|
||||
BinarySensorDevice)
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES, DOMAIN, BinarySensorDevice)
|
||||
from homeassistant.const import STATE_ON
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the mysensors platform for binary sensors."""
|
||||
"""Set up the MySensors platform for binary sensors."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
|
||||
add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(
|
||||
mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Represent the value of a MySensors Binary Sensor child node."""
|
||||
class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Representation of a MySensors Binary Sensor child node."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.mystrom/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice, DOMAIN)
|
||||
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY
|
||||
|
||||
@@ -37,7 +37,7 @@ class MyStromView(HomeAssistantView):
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request):
|
||||
"""The GET request received from a myStrom button."""
|
||||
"""Handle the GET request received from a myStrom button."""
|
||||
res = yield from self._handle(request.app['hass'], request.query)
|
||||
return res
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_CAMERAS, default=[]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_HOME): cv.string,
|
||||
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
|
||||
vol.Optional(CONF_PRESENCE_SENSORS, default=list(PRESENCE_SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES):
|
||||
vol.Optional(CONF_WELCOME_SENSORS, default=list(WELCOME_SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
@@ -131,10 +131,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
self._name += ' / ' + module_name
|
||||
self._sensor_name = sensor
|
||||
self._name += ' ' + sensor
|
||||
camera_id = data.camera_data.cameraByName(
|
||||
camera=camera_name, home=home)['id']
|
||||
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
|
||||
self._name, camera_id)
|
||||
self._cameratype = camera_type
|
||||
self._state = None
|
||||
|
||||
@@ -143,11 +139,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
"""Return the name of the Netatmo device and this sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID for this sensor."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
|
||||
@@ -27,7 +27,7 @@ SENSOR_TYPES = {
|
||||
}
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
@@ -97,7 +97,7 @@ class PilightBinarySensor(BinarySensorDevice):
|
||||
def _handle_code(self, call):
|
||||
"""Handle received code by the pilight-daemon.
|
||||
|
||||
If the code matches the defined playload
|
||||
If the code matches the defined payload
|
||||
of this sensor the sensor state is changed accordingly.
|
||||
"""
|
||||
# Check if received code matches defined playoad
|
||||
@@ -162,10 +162,10 @@ class PilightTriggerSensor(BinarySensorDevice):
|
||||
def _handle_code(self, call):
|
||||
"""Handle received code by the pilight-daemon.
|
||||
|
||||
If the code matches the defined playload
|
||||
If the code matches the defined payload
|
||||
of this sensor the sensor state is changed accordingly.
|
||||
"""
|
||||
# Check if received code matches defined playoad
|
||||
# Check if received code matches defined payload
|
||||
# True if payload is contained in received code dict
|
||||
payload_ok = True
|
||||
for key in self._payload:
|
||||
|
||||
@@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
sensor_type))
|
||||
|
||||
else:
|
||||
# create an sensor for each zone managed by faucet
|
||||
# create a sensor for each zone managed by faucet
|
||||
for zone in raincloud.controller.faucet.zones:
|
||||
sensors.append(RainCloudBinarySensor(zone, sensor_type))
|
||||
|
||||
|
||||
@@ -5,18 +5,17 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.raspihats/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_DEVICE_CLASS, DEVICE_DEFAULT_NAME
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA, BinarySensorDevice
|
||||
)
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.raspihats import (
|
||||
CONF_I2C_HATS, CONF_BOARD, CONF_ADDRESS, CONF_CHANNELS, CONF_INDEX,
|
||||
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException
|
||||
)
|
||||
CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX,
|
||||
CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the raspihats binary_sensor devices."""
|
||||
"""Set up the raspihats binary_sensor devices."""
|
||||
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
|
||||
binary_sensors = []
|
||||
i2c_hat_configs = config.get(CONF_I2C_HATS)
|
||||
@@ -65,39 +64,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
)
|
||||
)
|
||||
except I2CHatsException as ex:
|
||||
_LOGGER.error(
|
||||
"Failed to register " + board + "I2CHat@" + hex(address) + " "
|
||||
+ str(ex)
|
||||
)
|
||||
_LOGGER.error("Failed to register %s I2CHat@%s %s",
|
||||
board, hex(address), str(ex))
|
||||
add_devices(binary_sensors)
|
||||
|
||||
|
||||
class I2CHatBinarySensor(BinarySensorDevice):
|
||||
"""Represents a binary sensor that uses a I2C-HAT digital input."""
|
||||
"""Representation of a binary sensor that uses a I2C-HAT digital input."""
|
||||
|
||||
I2C_HATS_MANAGER = None
|
||||
|
||||
def __init__(self, address, channel, name, invert_logic, device_class):
|
||||
"""Initialize sensor."""
|
||||
"""Initialize the raspihats sensor."""
|
||||
self._address = address
|
||||
self._channel = channel
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._invert_logic = invert_logic
|
||||
self._device_class = device_class
|
||||
self._state = self.I2C_HATS_MANAGER.read_di(
|
||||
self._address,
|
||||
self._channel
|
||||
)
|
||||
self._address, self._channel)
|
||||
|
||||
def online_callback():
|
||||
"""Callback fired when board is online."""
|
||||
"""Call fired when board is online."""
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_online_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
online_callback
|
||||
)
|
||||
self._address, self._channel, online_callback)
|
||||
|
||||
def edge_callback(state):
|
||||
"""Read digital input state."""
|
||||
@@ -105,10 +97,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_di_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
edge_callback
|
||||
)
|
||||
self._address, self._channel, edge_callback)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@@ -122,7 +111,7 @@ class I2CHatBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling not needed for this sensor."""
|
||||
"""No polling needed for this sensor."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,86 +1,81 @@
|
||||
"""
|
||||
Support for RFXtrx binary sensors.
|
||||
|
||||
Lighting4 devices (sensors based on PT2262 encoder) are supported and
|
||||
tested. Other types may need some work.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.rfxtrx/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF, CONF_NAME)
|
||||
from homeassistant.components import rfxtrx
|
||||
from homeassistant.helpers import event as evt
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.components.rfxtrx import (
|
||||
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT,
|
||||
CONF_OFF_DELAY, CONF_DATA_BITS, CONF_DEVICES)
|
||||
from homeassistant.util import slugify
|
||||
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
|
||||
CONF_FIRE_EVENT, CONF_OFF_DELAY)
|
||||
from homeassistant.const import (
|
||||
CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import event as evt
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
|
||||
DEPENDENCIES = ["rfxtrx"]
|
||||
from homeassistant.util import slugify
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['rfxtrx']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_DEVICES, default={}): {
|
||||
cv.string: vol.Schema({
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS): cv.string,
|
||||
vol.Optional(CONF_DEVICE_CLASS):
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_OFF_DELAY, default=None):
|
||||
vol.Optional(CONF_OFF_DELAY):
|
||||
vol.Any(cv.time_period, cv.positive_timedelta),
|
||||
vol.Optional(CONF_DATA_BITS, default=None): cv.positive_int,
|
||||
vol.Optional(CONF_COMMAND_ON, default=None): cv.byte,
|
||||
vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte
|
||||
vol.Optional(CONF_DATA_BITS): cv.positive_int,
|
||||
vol.Optional(CONF_COMMAND_ON): cv.byte,
|
||||
vol.Optional(CONF_COMMAND_OFF): cv.byte
|
||||
})
|
||||
},
|
||||
vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
"""Setup the Binary Sensor platform to rfxtrx."""
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Binary Sensor platform to RFXtrx."""
|
||||
import RFXtrx as rfxtrxmod
|
||||
sensors = []
|
||||
|
||||
for packet_id, entity in config['devices'].items():
|
||||
for packet_id, entity in config[CONF_DEVICES].items():
|
||||
event = rfxtrx.get_rfx_object(packet_id)
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
|
||||
if device_id in rfxtrx.RFX_DEVICES:
|
||||
continue
|
||||
|
||||
if entity[CONF_DATA_BITS] is not None:
|
||||
_LOGGER.debug("Masked device id: %s",
|
||||
rfxtrx.get_pt2262_deviceid(device_id,
|
||||
entity[CONF_DATA_BITS]))
|
||||
if entity.get(CONF_DATA_BITS) is not None:
|
||||
_LOGGER.debug(
|
||||
"Masked device id: %s", rfxtrx.get_pt2262_deviceid(
|
||||
device_id, entity.get(CONF_DATA_BITS)))
|
||||
|
||||
_LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)",
|
||||
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
|
||||
entity[ATTR_NAME], entity.get(CONF_DEVICE_CLASS))
|
||||
|
||||
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
|
||||
entity[CONF_DEVICE_CLASS],
|
||||
entity[CONF_FIRE_EVENT],
|
||||
entity[CONF_OFF_DELAY],
|
||||
entity[CONF_DATA_BITS],
|
||||
entity[CONF_COMMAND_ON],
|
||||
entity[CONF_COMMAND_OFF])
|
||||
device = RfxtrxBinarySensor(
|
||||
event, entity.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS),
|
||||
entity[CONF_FIRE_EVENT], entity.get(CONF_OFF_DELAY),
|
||||
entity.get(CONF_DATA_BITS), entity.get(CONF_COMMAND_ON),
|
||||
entity.get(CONF_COMMAND_OFF))
|
||||
device.hass = hass
|
||||
sensors.append(device)
|
||||
rfxtrx.RFX_DEVICES[device_id] = device
|
||||
|
||||
add_devices_callback(sensors)
|
||||
add_devices(sensors)
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def binary_sensor_update(event):
|
||||
"""Callback for control updates from the RFXtrx gateway."""
|
||||
"""Call for control updates from the RFXtrx gateway."""
|
||||
if not isinstance(event, rfxtrxmod.ControlEvent):
|
||||
return
|
||||
|
||||
@@ -100,29 +95,26 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
|
||||
if poss_dev is not None:
|
||||
poss_id = slugify(poss_dev.event.device.id_string.lower())
|
||||
_LOGGER.debug("Found possible matching deviceid %s.",
|
||||
poss_id)
|
||||
_LOGGER.debug(
|
||||
"Found possible matching device ID: %s", poss_id)
|
||||
|
||||
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
|
||||
sensor = RfxtrxBinarySensor(event, pkt_id)
|
||||
sensor.hass = hass
|
||||
rfxtrx.RFX_DEVICES[device_id] = sensor
|
||||
add_devices_callback([sensor])
|
||||
_LOGGER.info("Added binary sensor %s "
|
||||
"(Device_id: %s Class: %s Sub: %s)",
|
||||
pkt_id,
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype)
|
||||
add_devices([sensor])
|
||||
_LOGGER.info(
|
||||
"Added binary sensor %s (Device ID: %s Class: %s Sub: %s)",
|
||||
pkt_id, slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__, event.device.subtype)
|
||||
|
||||
elif not isinstance(sensor, RfxtrxBinarySensor):
|
||||
return
|
||||
else:
|
||||
_LOGGER.debug("Binary sensor update "
|
||||
"(Device_id: %s Class: %s Sub: %s)",
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__,
|
||||
event.device.subtype)
|
||||
_LOGGER.debug(
|
||||
"Binary sensor update (Device ID: %s Class: %s Sub: %s)",
|
||||
slugify(event.device.id_string.lower()),
|
||||
event.device.__class__.__name__, event.device.subtype)
|
||||
|
||||
if sensor.is_lighting4:
|
||||
if sensor.data_bits is not None:
|
||||
@@ -142,22 +134,20 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
|
||||
sensor.update_state(False)
|
||||
|
||||
sensor.delay_listener = evt.track_point_in_time(
|
||||
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay
|
||||
)
|
||||
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay)
|
||||
|
||||
# Subscribe to main rfxtrx events
|
||||
# Subscribe to main RFXtrx events
|
||||
if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
|
||||
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(binary_sensor_update)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes,too-many-arguments
|
||||
class RfxtrxBinarySensor(BinarySensorDevice):
|
||||
"""An Rfxtrx binary sensor."""
|
||||
"""A representation of a RFXtrx binary sensor."""
|
||||
|
||||
def __init__(self, event, name, device_class=None,
|
||||
should_fire=False, off_delay=None, data_bits=None,
|
||||
cmd_on=None, cmd_off=None):
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the RFXtrx sensor."""
|
||||
self.event = event
|
||||
self._name = name
|
||||
self._should_fire_event = should_fire
|
||||
@@ -172,8 +162,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
|
||||
|
||||
if data_bits is not None:
|
||||
self._masked_id = rfxtrx.get_pt2262_deviceid(
|
||||
event.device.id_string.lower(),
|
||||
data_bits)
|
||||
event.device.id_string.lower(), data_bits)
|
||||
else:
|
||||
self._masked_id = None
|
||||
|
||||
|
||||
@@ -8,18 +8,17 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.rpi_pfio as rpi_pfio
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import DEVICE_DEFAULT_NAME
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
import homeassistant.components.rpi_pfio as rpi_pfio
|
||||
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_NAME = 'name'
|
||||
ATTR_INVERT_LOGIC = 'invert_logic'
|
||||
ATTR_SETTLE_TIME = 'settle_time'
|
||||
CONF_INVERT_LOGIC = 'invert_logic'
|
||||
CONF_PORTS = 'ports'
|
||||
CONF_SETTLE_TIME = 'settle_time'
|
||||
|
||||
DEFAULT_INVERT_LOGIC = False
|
||||
DEFAULT_SETTLE_TIME = 20
|
||||
@@ -27,27 +26,27 @@ DEFAULT_SETTLE_TIME = 20
|
||||
DEPENDENCIES = ['rpi_pfio']
|
||||
|
||||
PORT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_NAME, default=None): cv.string,
|
||||
vol.Optional(ATTR_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
|
||||
cv.positive_int,
|
||||
vol.Optional(ATTR_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean
|
||||
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PORTS, default={}): vol.Schema({
|
||||
cv.positive_int: PORT_SCHEMA
|
||||
cv.positive_int: PORT_SCHEMA,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the PiFace Digital Input devices."""
|
||||
"""Set up the PiFace Digital Input devices."""
|
||||
binary_sensors = []
|
||||
ports = config.get('ports')
|
||||
ports = config.get(CONF_PORTS)
|
||||
for port, port_entity in ports.items():
|
||||
name = port_entity[ATTR_NAME]
|
||||
settle_time = port_entity[ATTR_SETTLE_TIME] / 1000
|
||||
invert_logic = port_entity[ATTR_INVERT_LOGIC]
|
||||
name = port_entity.get(CONF_NAME)
|
||||
settle_time = port_entity[CONF_SETTLE_TIME] / 1000
|
||||
invert_logic = port_entity[CONF_INVERT_LOGIC]
|
||||
|
||||
binary_sensors.append(RPiPFIOBinarySensor(
|
||||
hass, port, name, settle_time, invert_logic))
|
||||
|
||||
@@ -4,46 +4,49 @@ 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/binary_sensor.spc/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.spc import (
|
||||
ATTR_DISCOVER_DEVICES, DATA_REGISTRY)
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import (STATE_UNAVAILABLE, STATE_ON, STATE_OFF)
|
||||
|
||||
from homeassistant.components.spc import ATTR_DISCOVER_DEVICES, DATA_REGISTRY
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion',
|
||||
'1': 'opening',
|
||||
'3': 'smoke'}
|
||||
SPC_TYPE_TO_DEVICE_CLASS = {
|
||||
'0': 'motion',
|
||||
'1': 'opening',
|
||||
'3': 'smoke',
|
||||
}
|
||||
|
||||
|
||||
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF,
|
||||
'1': STATE_ON}
|
||||
SPC_INPUT_TO_SENSOR_STATE = {
|
||||
'0': STATE_OFF,
|
||||
'1': STATE_ON,
|
||||
}
|
||||
|
||||
|
||||
def _get_device_class(spc_type):
|
||||
"""Get the device class."""
|
||||
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
|
||||
|
||||
|
||||
def _get_sensor_state(spc_input):
|
||||
"""Get the sensor state."""
|
||||
return SPC_INPUT_TO_SENSOR_STATE.get(spc_input, STATE_UNAVAILABLE)
|
||||
|
||||
|
||||
def _create_sensor(hass, zone):
|
||||
return SpcBinarySensor(zone_id=zone['id'],
|
||||
name=zone['zone_name'],
|
||||
state=_get_sensor_state(zone['input']),
|
||||
device_class=_get_device_class(zone['type']),
|
||||
spc_registry=hass.data[DATA_REGISTRY])
|
||||
"""Create a SPC sensor."""
|
||||
return SpcBinarySensor(
|
||||
zone_id=zone['id'], name=zone['zone_name'],
|
||||
state=_get_sensor_state(zone['input']),
|
||||
device_class=_get_device_class(zone['type']),
|
||||
spc_registry=hass.data[DATA_REGISTRY])
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the SPC binary sensor."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
return
|
||||
@@ -55,7 +58,7 @@ def async_setup_platform(hass, config, async_add_devices,
|
||||
|
||||
|
||||
class SpcBinarySensor(BinarySensorDevice):
|
||||
"""Represents a sensor based on an SPC zone."""
|
||||
"""Representation of a sensor based on a SPC zone."""
|
||||
|
||||
def __init__(self, zone_id, name, state, device_class, spc_registry):
|
||||
"""Initialize the sensor device."""
|
||||
@@ -74,7 +77,7 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the device."""
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
@@ -85,7 +88,7 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def hidden(self) -> bool:
|
||||
"""Whether the device is hidden by default."""
|
||||
# these type of sensors are probably mainly used for automations
|
||||
# These type of sensors are probably mainly used for automations
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -95,5 +98,5 @@ class SpcBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""The device class."""
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
||||
@@ -4,15 +4,15 @@ Support for Taps Affs.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.tapsaff/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import (CONF_NAME)
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['tapsaff==0.1.3']
|
||||
|
||||
@@ -67,7 +67,7 @@ class TapsAffData(object):
|
||||
"""Class for handling the data retrieval for pins."""
|
||||
|
||||
def __init__(self, location):
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the data object."""
|
||||
from tapsaff import TapsAff
|
||||
|
||||
self._is_taps_aff = None
|
||||
|
||||
@@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||
CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE,
|
||||
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@@ -29,6 +30,8 @@ CONF_DELAY_OFF = 'delay_off'
|
||||
|
||||
SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
@@ -38,11 +41,6 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
vol.All(cv.time_period, cv.positive_timedelta),
|
||||
})
|
||||
|
||||
SENSOR_SCHEMA = vol.All(
|
||||
cv.deprecated(ATTR_ENTITY_ID),
|
||||
SENSOR_SCHEMA,
|
||||
)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
|
||||
})
|
||||
@@ -55,6 +53,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
value_template = device_config[CONF_VALUE_TEMPLATE]
|
||||
icon_template = device_config.get(CONF_ICON_TEMPLATE)
|
||||
entity_picture_template = device_config.get(
|
||||
CONF_ENTITY_PICTURE_TEMPLATE)
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
value_template.extract_entities())
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
@@ -65,10 +66,17 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
if icon_template is not None:
|
||||
icon_template.hass = hass
|
||||
|
||||
if entity_picture_template is not None:
|
||||
entity_picture_template.hass = hass
|
||||
|
||||
sensors.append(
|
||||
BinarySensorTemplate(
|
||||
hass, device, friendly_name, device_class, value_template,
|
||||
entity_ids, delay_on, delay_off)
|
||||
icon_template, entity_picture_template, entity_ids,
|
||||
delay_on, delay_off)
|
||||
)
|
||||
if not sensors:
|
||||
_LOGGER.error("No sensors added")
|
||||
@@ -82,7 +90,8 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
"""A virtual binary sensor that triggers from another sensor."""
|
||||
|
||||
def __init__(self, hass, device, friendly_name, device_class,
|
||||
value_template, entity_ids, delay_on, delay_off):
|
||||
value_template, icon_template, entity_picture_template,
|
||||
entity_ids, delay_on, delay_off):
|
||||
"""Initialize the Template binary sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = async_generate_entity_id(
|
||||
@@ -91,6 +100,10 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
self._device_class = device_class
|
||||
self._template = value_template
|
||||
self._state = None
|
||||
self._icon_template = icon_template
|
||||
self._entity_picture_template = entity_picture_template
|
||||
self._icon = None
|
||||
self._entity_picture = None
|
||||
self._entities = entity_ids
|
||||
self._delay_on = delay_on
|
||||
self._delay_off = delay_off
|
||||
@@ -119,6 +132,16 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def entity_picture(self):
|
||||
"""Return the entity_picture to use in the frontend, if any."""
|
||||
return self._entity_picture
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
@@ -137,8 +160,9 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
@callback
|
||||
def _async_render(self):
|
||||
"""Get the state of template."""
|
||||
state = None
|
||||
try:
|
||||
return self._template.async_render().lower() == 'true'
|
||||
state = (self._template.async_render().lower() == 'true')
|
||||
except TemplateError as ex:
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
@@ -148,6 +172,29 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
return
|
||||
_LOGGER.error("Could not render template %s: %s", self._name, ex)
|
||||
|
||||
for property_name, template in (
|
||||
('_icon', self._icon_template),
|
||||
('_entity_picture', self._entity_picture_template)):
|
||||
if template is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
setattr(self, property_name, template.async_render())
|
||||
except TemplateError as ex:
|
||||
friendly_property_name = property_name[1:].replace('_', ' ')
|
||||
if ex.args and ex.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"):
|
||||
# Common during HA startup - so just a warning
|
||||
_LOGGER.warning('Could not render %s template %s,'
|
||||
' the state is unknown.',
|
||||
friendly_property_name, self._name)
|
||||
else:
|
||||
_LOGGER.error('Could not render %s template %s: %s',
|
||||
friendly_property_name, self._name, ex)
|
||||
return state
|
||||
|
||||
return state
|
||||
|
||||
@callback
|
||||
def async_check_state(self):
|
||||
"""Update the state from the template."""
|
||||
|
||||
@@ -28,7 +28,7 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
|
||||
"""Implement an Tesla binary sensor for parking and charger."""
|
||||
|
||||
def __init__(self, tesla_device, controller, sensor_type):
|
||||
"""Initialisation of binary sensor."""
|
||||
"""Initialise of a Tesla binary sensor."""
|
||||
super().__init__(tesla_device, controller)
|
||||
self._state = False
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
|
||||
|
||||
@@ -126,11 +126,12 @@ class ThresholdSensor(BinarySensorDevice):
|
||||
@property
|
||||
def threshold_type(self):
|
||||
"""Return the type of threshold this sensor represents."""
|
||||
if self._threshold_lower and self._threshold_upper:
|
||||
if self._threshold_lower is not None and \
|
||||
self._threshold_upper is not None:
|
||||
return TYPE_RANGE
|
||||
elif self._threshold_lower:
|
||||
elif self._threshold_lower is not None:
|
||||
return TYPE_LOWER
|
||||
elif self._threshold_upper:
|
||||
elif self._threshold_upper is not None:
|
||||
return TYPE_UPPER
|
||||
|
||||
@property
|
||||
|
||||
38
homeassistant/components/binary_sensor/upcloud.py
Normal file
38
homeassistant/components/binary_sensor/upcloud.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Support for monitoring the state of UpCloud servers.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.upcloud/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.upcloud import (
|
||||
UpCloudServerEntity, CONF_SERVERS, DATA_UPCLOUD)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['upcloud']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string]),
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the UpCloud server binary sensor."""
|
||||
upcloud = hass.data[DATA_UPCLOUD]
|
||||
|
||||
servers = config.get(CONF_SERVERS)
|
||||
|
||||
devices = [UpCloudBinarySensor(upcloud, uuid) for uuid in servers]
|
||||
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class UpCloudBinarySensor(UpCloudServerEntity, BinarySensorDevice):
|
||||
"""Representation of an UpCloud server sensor."""
|
||||
@@ -6,15 +6,15 @@ https://home-assistant.io/components/binary_sensor.verisure/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.verisure import CONF_DOOR_WINDOW
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Verisure binary sensors."""
|
||||
"""Set up the Verisure binary sensors."""
|
||||
sensors = []
|
||||
hub.update_overview()
|
||||
|
||||
@@ -27,10 +27,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class VerisureDoorWindowSensor(BinarySensorDevice):
|
||||
"""Verisure door window sensor."""
|
||||
"""Representation of a Verisure door window sensor."""
|
||||
|
||||
def __init__(self, device_label):
|
||||
"""Initialize the modbus coil sensor."""
|
||||
"""Initialize the Verisure door window sensor."""
|
||||
self._device_label = device_label
|
||||
|
||||
@property
|
||||
|
||||
@@ -31,7 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Vultr subscription (server) sensor."""
|
||||
"""Set up the Vultr subscription (server) binary sensor."""
|
||||
vultr = hass.data[DATA_VULTR]
|
||||
|
||||
subscription = config.get(CONF_SUBSCRIPTION)
|
||||
@@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
if subscription not in vultr.data:
|
||||
_LOGGER.error("Subscription %s not found", subscription)
|
||||
return False
|
||||
return
|
||||
|
||||
add_devices([VultrBinarySensor(vultr, subscription, name)], True)
|
||||
|
||||
@@ -48,7 +48,7 @@ class VultrBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Vultr subscription sensor."""
|
||||
|
||||
def __init__(self, vultr, subscription, name):
|
||||
"""Initialize a new Vultr sensor."""
|
||||
"""Initialize a new Vultr binary sensor."""
|
||||
self._vultr = vultr
|
||||
self._name = name
|
||||
|
||||
|
||||
@@ -58,11 +58,11 @@ class WemoBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the id of this WeMo device."""
|
||||
return '{}.{}'.format(self.__class__, self.wemo.serialnumber)
|
||||
return self.wemo.serialnumber
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sevice if any."""
|
||||
"""Return the name of the service if any."""
|
||||
return self.wemo.name
|
||||
|
||||
@property
|
||||
|
||||
@@ -8,7 +8,7 @@ import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
from homeassistant.components.wink import DOMAIN, WinkDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -16,18 +16,18 @@ DEPENDENCIES = ['wink']
|
||||
|
||||
# These are the available sensors mapped to binary_sensor class
|
||||
SENSOR_TYPES = {
|
||||
'opened': 'opening',
|
||||
'brightness': 'light',
|
||||
'vibration': 'vibration',
|
||||
'loudness': 'sound',
|
||||
'noise': 'sound',
|
||||
'capturing_audio': 'sound',
|
||||
'liquid_detected': 'moisture',
|
||||
'motion': 'motion',
|
||||
'presence': 'occupancy',
|
||||
'capturing_video': None,
|
||||
'co_detected': 'gas',
|
||||
'liquid_detected': 'moisture',
|
||||
'loudness': 'sound',
|
||||
'motion': 'motion',
|
||||
'noise': 'sound',
|
||||
'opened': 'opening',
|
||||
'presence': 'occupancy',
|
||||
'smoke_detected': 'smoke',
|
||||
'capturing_video': None
|
||||
'vibration': 'vibration',
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Callback when entity is added to hass."""
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)
|
||||
|
||||
@property
|
||||
@@ -118,7 +118,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
return super().device_state_attributes
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['test_activated'] = self.wink.test_activated()
|
||||
return _attributes
|
||||
@@ -138,11 +138,18 @@ class WinkHub(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['update_needed'] = self.wink.update_needed()
|
||||
_attributes['firmware_version'] = self.wink.firmware_version()
|
||||
_attributes['pairing_mode'] = self.wink.pairing_mode()
|
||||
_kidde_code = self.wink.kidde_radio_code()
|
||||
if _kidde_code is not None:
|
||||
# The service call to set the Kidde code
|
||||
# takes a string of 1s and 0s so it makes
|
||||
# sense to display it to the user that way
|
||||
_formatted_kidde_code = "{:b}".format(_kidde_code).zfill(8)
|
||||
_attributes['kidde_radio_code'] = _formatted_kidde_code
|
||||
return _attributes
|
||||
|
||||
|
||||
@@ -170,7 +177,7 @@ class WinkButton(WinkBinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
"""Return the device state attributes."""
|
||||
_attributes = super().device_state_attributes
|
||||
_attributes['pressed'] = self.wink.pressed()
|
||||
_attributes['long_pressed'] = self.wink.long_pressed()
|
||||
|
||||
@@ -17,17 +17,21 @@ import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['holidays==0.8.1']
|
||||
REQUIREMENTS = ['holidays==0.9.3']
|
||||
|
||||
# List of all countries currently supported by holidays
|
||||
# There seems to be no way to get the list out at runtime
|
||||
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA',
|
||||
'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England',
|
||||
'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE',
|
||||
'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL',
|
||||
'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO',
|
||||
'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain',
|
||||
'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales']
|
||||
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada',
|
||||
'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK',
|
||||
'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland',
|
||||
'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland',
|
||||
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
|
||||
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
|
||||
'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
|
||||
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
|
||||
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',
|
||||
'Sweden', 'SE', 'UnitedKingdom', 'UK', 'UnitedStates', 'US',
|
||||
'Wales']
|
||||
CONF_COUNTRY = 'country'
|
||||
CONF_PROVINCE = 'province'
|
||||
CONF_WORKDAYS = 'workdays'
|
||||
@@ -43,7 +47,7 @@ DEFAULT_OFFSET = 0
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
|
||||
vol.Optional(CONF_PROVINCE, default=None): cv.string,
|
||||
vol.Optional(CONF_PROVINCE): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
|
||||
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):
|
||||
|
||||
@@ -101,7 +101,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if DENSITY in data:
|
||||
self._density = int(data.get(DENSITY))
|
||||
@@ -139,8 +139,16 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if raw_data['cmd'] == 'heartbeat':
|
||||
_LOGGER.debug(
|
||||
'Skipping heartbeat of the motion sensor. '
|
||||
'It can introduce an incorrect state because of a firmware '
|
||||
'bug (https://github.com/home-assistant/home-assistant/pull/'
|
||||
'11631#issuecomment-357507744).')
|
||||
return
|
||||
|
||||
self._should_poll = False
|
||||
if NO_MOTION in data: # handle push from the hub
|
||||
self._no_motion_since = data[NO_MOTION]
|
||||
@@ -186,7 +194,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
self._should_poll = False
|
||||
if NO_CLOSE in data: # handle push from the hub
|
||||
@@ -219,7 +227,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
|
||||
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
|
||||
xiaomi_hub, 'status', 'moisture')
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
self._should_poll = False
|
||||
|
||||
@@ -256,7 +264,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if DENSITY in data:
|
||||
self._density = int(data.get(DENSITY))
|
||||
@@ -293,7 +301,7 @@ class XiaomiButton(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
value = data.get(self._data_key)
|
||||
if value is None:
|
||||
@@ -311,7 +319,10 @@ class XiaomiButton(XiaomiBinarySensor):
|
||||
click_type = 'double'
|
||||
elif value == 'both_click':
|
||||
click_type = 'both'
|
||||
elif value == 'shake':
|
||||
click_type = 'shake'
|
||||
else:
|
||||
_LOGGER.warning("Unsupported click_type detected: %s", value)
|
||||
return False
|
||||
|
||||
self._hass.bus.fire('click', {
|
||||
@@ -343,7 +354,7 @@ class XiaomiCube(XiaomiBinarySensor):
|
||||
attrs.update(super().device_state_attributes)
|
||||
return attrs
|
||||
|
||||
def parse_data(self, data):
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
if 'status' in data:
|
||||
self._hass.bus.fire('cube_action', {
|
||||
|
||||
@@ -4,7 +4,6 @@ Binary sensors on Zigbee Home Automation networks.
|
||||
For more details on this platform, please refer to the documentation
|
||||
at https://home-assistant.io/components/binary_sensor.zha/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
|
||||
@@ -25,33 +24,33 @@ CLASS_MAPPING = {
|
||||
}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
async def async_setup_platform(hass, config, async_add_devices,
|
||||
discovery_info=None):
|
||||
"""Set up the Zigbee Home Automation binary sensors."""
|
||||
discovery_info = zha.get_discovery_info(hass, discovery_info)
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
from bellows.zigbee.zcl.clusters.security import IasZone
|
||||
from zigpy.zcl.clusters.security import IasZone
|
||||
|
||||
in_clusters = discovery_info['in_clusters']
|
||||
|
||||
device_class = None
|
||||
cluster = in_clusters[IasZone.cluster_id]
|
||||
if discovery_info['new_join']:
|
||||
yield from cluster.bind()
|
||||
await cluster.bind()
|
||||
ieee = cluster.endpoint.device.application.ieee
|
||||
yield from cluster.write_attributes({'cie_addr': ieee})
|
||||
await cluster.write_attributes({'cie_addr': ieee})
|
||||
|
||||
try:
|
||||
zone_type = yield from cluster['zone_type']
|
||||
zone_type = await cluster['zone_type']
|
||||
device_class = CLASS_MAPPING.get(zone_type, None)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# If we fail to read from the device, use a non-specific class
|
||||
pass
|
||||
|
||||
sensor = BinarySensor(device_class, **discovery_info)
|
||||
async_add_devices([sensor])
|
||||
async_add_devices([sensor], update_before_add=True)
|
||||
|
||||
|
||||
class BinarySensor(zha.Entity, BinarySensorDevice):
|
||||
@@ -63,9 +62,14 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
|
||||
"""Initialize the ZHA binary sensor."""
|
||||
super().__init__(**kwargs)
|
||||
self._device_class = device_class
|
||||
from bellows.zigbee.zcl.clusters.security import IasZone
|
||||
from zigpy.zcl.clusters.security import IasZone
|
||||
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Let zha handle polling."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if entity is on."""
|
||||
@@ -78,12 +82,23 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return self._device_class
|
||||
|
||||
def cluster_command(self, aps_frame, tsn, command_id, args):
|
||||
def cluster_command(self, tsn, command_id, args):
|
||||
"""Handle commands received to this cluster."""
|
||||
if command_id == 0:
|
||||
self._state = args[0] & 3
|
||||
_LOGGER.debug("Updated alarm state: %s", self._state)
|
||||
self.schedule_update_ha_state()
|
||||
self.async_schedule_update_ha_state()
|
||||
elif command_id == 1:
|
||||
_LOGGER.debug("Enroll requested")
|
||||
self.hass.add_job(self._ias_zone_cluster.enroll_response(0, 0))
|
||||
res = self._ias_zone_cluster.enroll_response(0, 0)
|
||||
self.hass.async_add_job(res)
|
||||
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
from bellows.types.basic import uint16_t
|
||||
|
||||
result = await zha.safe_read(self._endpoint.ias_zone,
|
||||
['zone_status'])
|
||||
state = result.get('zone_status', self._state)
|
||||
if isinstance(state, (int, uint16_t)):
|
||||
self._state = result.get('zone_status', self._state) & 3
|
||||
|
||||
105
homeassistant/components/bmw_connected_drive.py
Normal file
105
homeassistant/components/bmw_connected_drive.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
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/bmw_connected_drive/
|
||||
"""
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_USERNAME, CONF_PASSWORD
|
||||
)
|
||||
|
||||
REQUIREMENTS = ['bimmer_connected==0.4.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'bmw_connected_drive'
|
||||
CONF_VALUES = 'values'
|
||||
CONF_COUNTRY = 'country'
|
||||
|
||||
ACCOUNT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_COUNTRY): cv.string,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
cv.string: ACCOUNT_SCHEMA
|
||||
},
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
BMW_COMPONENTS = ['device_tracker', 'sensor']
|
||||
UPDATE_INTERVAL = 5 # in minutes
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the BMW connected drive components."""
|
||||
accounts = []
|
||||
for name, account_config in config[DOMAIN].items():
|
||||
username = account_config[CONF_USERNAME]
|
||||
password = account_config[CONF_PASSWORD]
|
||||
country = account_config[CONF_COUNTRY]
|
||||
_LOGGER.debug('Adding new account %s', name)
|
||||
bimmer = BMWConnectedDriveAccount(username, password, country, name)
|
||||
accounts.append(bimmer)
|
||||
|
||||
# update every UPDATE_INTERVAL minutes, starting now
|
||||
# this should even out the load on the servers
|
||||
|
||||
now = datetime.datetime.now()
|
||||
track_utc_time_change(
|
||||
hass, bimmer.update,
|
||||
minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL),
|
||||
second=now.second)
|
||||
|
||||
hass.data[DOMAIN] = accounts
|
||||
|
||||
for account in accounts:
|
||||
account.update()
|
||||
|
||||
for component in BMW_COMPONENTS:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class BMWConnectedDriveAccount(object):
|
||||
"""Representation of a BMW vehicle."""
|
||||
|
||||
def __init__(self, username: str, password: str, country: str,
|
||||
name: str) -> None:
|
||||
"""Constructor."""
|
||||
from bimmer_connected.account import ConnectedDriveAccount
|
||||
|
||||
self.account = ConnectedDriveAccount(username, password, country)
|
||||
self.name = name
|
||||
self._update_listeners = []
|
||||
|
||||
def update(self, *_):
|
||||
"""Update the state of all vehicles.
|
||||
|
||||
Notify all listeners about the update.
|
||||
"""
|
||||
_LOGGER.debug('Updating vehicle state for account %s, '
|
||||
'notifying %d listeners',
|
||||
self.name, len(self._update_listeners))
|
||||
try:
|
||||
self.account.update_vehicle_states()
|
||||
for listener in self._update_listeners:
|
||||
listener()
|
||||
except IOError as exception:
|
||||
_LOGGER.error('Error updating the vehicle state.')
|
||||
_LOGGER.exception(exception)
|
||||
|
||||
def add_update_listener(self, listener):
|
||||
"""Add a listener for update notifications."""
|
||||
self._update_listeners.append(listener)
|
||||
@@ -4,18 +4,18 @@ Support for WebDav Calendar.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/calendar.caldav/
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.calendar import (
|
||||
CalendarEventDevice, PLATFORM_SCHEMA)
|
||||
PLATFORM_SCHEMA, CalendarEventDevice)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
|
||||
from homeassistant.util import dt, Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle, dt
|
||||
|
||||
REQUIREMENTS = ['caldav==0.5.0']
|
||||
|
||||
@@ -39,9 +39,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_CUSTOM_CALENDARS, default=[]):
|
||||
vol.All(cv.ensure_list, vol.Schema([
|
||||
vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_CALENDAR): cv.string,
|
||||
vol.Required(CONF_SEARCH): cv.string
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_SEARCH): cv.string,
|
||||
})
|
||||
]))
|
||||
})
|
||||
@@ -53,12 +53,12 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
||||
"""Set up the WebDav Calendar platform."""
|
||||
import caldav
|
||||
|
||||
client = caldav.DAVClient(config.get(CONF_URL),
|
||||
None,
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD))
|
||||
url = config.get(CONF_URL)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
client = caldav.DAVClient(url, None, username, password)
|
||||
|
||||
# Retrieve all the remote calendars
|
||||
calendars = client.principal().calendars()
|
||||
|
||||
calendar_devices = []
|
||||
@@ -70,8 +70,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
||||
_LOGGER.debug("Ignoring calendar '%s'", calendar.name)
|
||||
continue
|
||||
|
||||
# Create additional calendars based on custom filtering
|
||||
# rules
|
||||
# Create additional calendars based on custom filtering rules
|
||||
for cust_calendar in config.get(CONF_CUSTOM_CALENDARS):
|
||||
# Check that the base calendar matches
|
||||
if cust_calendar.get(CONF_CALENDAR) != calendar.name:
|
||||
@@ -85,12 +84,9 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
||||
}
|
||||
|
||||
calendar_devices.append(
|
||||
WebDavCalendarEventDevice(hass,
|
||||
device_data,
|
||||
calendar,
|
||||
True,
|
||||
cust_calendar.get(CONF_SEARCH))
|
||||
)
|
||||
WebDavCalendarEventDevice(
|
||||
hass, device_data, calendar, True,
|
||||
cust_calendar.get(CONF_SEARCH)))
|
||||
|
||||
# Create a default calendar if there was no custom one
|
||||
if not config.get(CONF_CUSTOM_CALENDARS):
|
||||
@@ -102,18 +98,13 @@ def setup_platform(hass, config, add_devices, disc_info=None):
|
||||
WebDavCalendarEventDevice(hass, device_data, calendar)
|
||||
)
|
||||
|
||||
# Finally add all the calendars we've created
|
||||
add_devices(calendar_devices)
|
||||
|
||||
|
||||
class WebDavCalendarEventDevice(CalendarEventDevice):
|
||||
"""A device for getting the next Task from a WebDav Calendar."""
|
||||
|
||||
def __init__(self,
|
||||
hass,
|
||||
device_data,
|
||||
calendar,
|
||||
all_day=False,
|
||||
def __init__(self, hass, device_data, calendar, all_day=False,
|
||||
search=None):
|
||||
"""Create the WebDav Calendar Event Device."""
|
||||
self.data = WebDavCalendarData(calendar, all_day, search)
|
||||
@@ -167,9 +158,7 @@ class WebDavCalendarData(object):
|
||||
if vevent is None:
|
||||
_LOGGER.debug(
|
||||
"No matching event found in the %d results for %s",
|
||||
len(results),
|
||||
self.calendar.name,
|
||||
)
|
||||
len(results), self.calendar.name)
|
||||
self.event = None
|
||||
return True
|
||||
|
||||
@@ -177,7 +166,7 @@ class WebDavCalendarData(object):
|
||||
self.event = {
|
||||
"summary": vevent.summary.value,
|
||||
"start": self.get_hass_date(vevent.dtstart.value),
|
||||
"end": self.get_hass_date(vevent.dtend.value),
|
||||
"end": self.get_hass_date(self.get_end_date(vevent)),
|
||||
"location": self.get_attr_value(vevent, "location"),
|
||||
"description": self.get_attr_value(vevent, "description")
|
||||
}
|
||||
@@ -185,7 +174,7 @@ class WebDavCalendarData(object):
|
||||
|
||||
@staticmethod
|
||||
def is_matching(vevent, search):
|
||||
"""Return if the event matches the filter critera."""
|
||||
"""Return if the event matches the filter criteria."""
|
||||
if search is None:
|
||||
return True
|
||||
|
||||
@@ -205,7 +194,7 @@ class WebDavCalendarData(object):
|
||||
@staticmethod
|
||||
def is_over(vevent):
|
||||
"""Return if the event is over."""
|
||||
return dt.now() > WebDavCalendarData.to_datetime(vevent.dtend.value)
|
||||
return dt.now() > WebDavCalendarData.get_end_date(vevent)
|
||||
|
||||
@staticmethod
|
||||
def get_hass_date(obj):
|
||||
@@ -228,3 +217,17 @@ class WebDavCalendarData(object):
|
||||
if hasattr(obj, attribute):
|
||||
return getattr(obj, attribute).value
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_end_date(obj):
|
||||
"""Return the end datetime as determined by dtend or duration."""
|
||||
if hasattr(obj, "dtend"):
|
||||
enddate = obj.dtend.value
|
||||
|
||||
elif hasattr(obj, "duration"):
|
||||
enddate = obj.dtstart.value + obj.duration.value
|
||||
|
||||
else:
|
||||
enddate = obj.dtstart.value + timedelta(days=1)
|
||||
|
||||
return WebDavCalendarData.to_datetime(enddate)
|
||||
|
||||
@@ -4,29 +4,28 @@ Support for Todoist task management (https://todoist.com).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/calendar.todoist/
|
||||
"""
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.calendar import (
|
||||
CalendarEventDevice, DOMAIN, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.google import (
|
||||
CONF_DEVICE_ID)
|
||||
from homeassistant.const import (
|
||||
CONF_ID, CONF_NAME, CONF_TOKEN)
|
||||
DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice)
|
||||
from homeassistant.components.google import CONF_DEVICE_ID
|
||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||
from homeassistant.util import dt
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.util import Throttle, dt
|
||||
|
||||
REQUIREMENTS = ['todoist-python==7.0.17']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_EXTRA_PROJECTS = 'custom_projects'
|
||||
CONF_PROJECT_DUE_DATE = 'due_date_days'
|
||||
CONF_PROJECT_LABEL_WHITELIST = 'labels'
|
||||
CONF_PROJECT_WHITELIST = 'include_projects'
|
||||
|
||||
# Calendar Platform: Does this calendar event last all day?
|
||||
ALL_DAY = 'all_day'
|
||||
# Attribute: All tasks in this project
|
||||
@@ -78,20 +77,15 @@ SUMMARY = 'summary'
|
||||
TASKS = 'items'
|
||||
|
||||
SERVICE_NEW_TASK = 'todoist_new_task'
|
||||
|
||||
NEW_TASK_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONTENT): cv.string,
|
||||
vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower),
|
||||
vol.Optional(LABELS): cv.ensure_list_csv,
|
||||
vol.Optional(PRIORITY): vol.All(vol.Coerce(int),
|
||||
vol.Range(min=1, max=4)),
|
||||
vol.Optional(DUE_DATE): cv.string
|
||||
vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
|
||||
vol.Optional(DUE_DATE): cv.string,
|
||||
})
|
||||
|
||||
CONF_EXTRA_PROJECTS = 'custom_projects'
|
||||
CONF_PROJECT_DUE_DATE = 'due_date_days'
|
||||
CONF_PROJECT_WHITELIST = 'include_projects'
|
||||
CONF_PROJECT_LABEL_WHITELIST = 'labels'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_TOKEN): cv.string,
|
||||
vol.Optional(CONF_EXTRA_PROJECTS, default=[]):
|
||||
@@ -111,8 +105,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Todoist platform."""
|
||||
# Check token:
|
||||
"""Set up the Todoist platform."""
|
||||
token = config.get(CONF_TOKEN)
|
||||
|
||||
# Look up IDs based on (lowercase) names.
|
||||
@@ -176,7 +169,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
add_devices(project_devices)
|
||||
|
||||
def handle_new_task(call):
|
||||
"""Called when a user creates a new Todoist Task from HASS."""
|
||||
"""Call when a user creates a new Todoist Task from HASS."""
|
||||
project_name = call.data[PROJECT_NAME]
|
||||
project_id = project_id_lookup[project_name]
|
||||
|
||||
@@ -418,7 +411,7 @@ class TodoistProjectData(object):
|
||||
|
||||
The "best" event is determined by the following criteria:
|
||||
* A proposed event must not be completed
|
||||
* A proposed event must have a end date (otherwise we go with
|
||||
* A proposed event must have an end date (otherwise we go with
|
||||
the event at index 0, selected above)
|
||||
* A proposed event must be on the same day or earlier as our
|
||||
current event
|
||||
@@ -505,7 +498,7 @@ class TodoistProjectData(object):
|
||||
|
||||
# Organize the best tasks (so users can see all the tasks
|
||||
# they have, organized)
|
||||
while len(project_tasks) > 0:
|
||||
while project_tasks:
|
||||
best_task = self.select_best_task(project_tasks)
|
||||
_LOGGER.debug("Found Todoist Task: %s", best_task[SUMMARY])
|
||||
project_tasks.remove(best_task)
|
||||
@@ -528,8 +521,7 @@ class TodoistProjectData(object):
|
||||
# Let's set our "due date" to tomorrow
|
||||
self.event[END] = {
|
||||
DATETIME: (
|
||||
datetime.utcnow() +
|
||||
timedelta(days=1)
|
||||
datetime.utcnow() + timedelta(days=1)
|
||||
).strftime(DATE_STR_FORMAT)
|
||||
}
|
||||
_LOGGER.debug("Updated %s", self._name)
|
||||
|
||||
@@ -91,13 +91,13 @@ def async_snapshot(hass, filename, entity_id=None):
|
||||
@bind_hass
|
||||
@asyncio.coroutine
|
||||
def async_get_image(hass, entity_id, timeout=10):
|
||||
"""Fetch a image from a camera entity."""
|
||||
"""Fetch an image from a camera entity."""
|
||||
websession = async_get_clientsession(hass)
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
if state is None:
|
||||
raise HomeAssistantError(
|
||||
"No entity '{0}' for grab a image".format(entity_id))
|
||||
"No entity '{0}' for grab an image".format(entity_id))
|
||||
|
||||
url = "{0}{1}".format(
|
||||
hass.config.api.base_url,
|
||||
@@ -124,15 +124,15 @@ def async_setup(hass, config):
|
||||
"""Set up the camera component."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
|
||||
|
||||
hass.http.register_view(CameraImageView(component.entities))
|
||||
hass.http.register_view(CameraMjpegStream(component.entities))
|
||||
hass.http.register_view(CameraImageView(component))
|
||||
hass.http.register_view(CameraMjpegStream(component))
|
||||
|
||||
yield from component.async_setup(config)
|
||||
|
||||
@callback
|
||||
def update_tokens(time):
|
||||
"""Update tokens of the entities."""
|
||||
for entity in component.entities.values():
|
||||
for entity in component.entities:
|
||||
entity.async_update_token()
|
||||
hass.async_add_job(entity.async_update_ha_state())
|
||||
|
||||
@@ -264,9 +264,9 @@ class Camera(Entity):
|
||||
'boundary=--frameboundary')
|
||||
yield from response.prepare(request)
|
||||
|
||||
def write(img_bytes):
|
||||
async def write(img_bytes):
|
||||
"""Write image to stream."""
|
||||
response.write(bytes(
|
||||
await response.write(bytes(
|
||||
'--frameboundary\r\n'
|
||||
'Content-Type: {}\r\n'
|
||||
'Content-Length: {}\r\n\r\n'.format(
|
||||
@@ -282,15 +282,14 @@ class Camera(Entity):
|
||||
break
|
||||
|
||||
if img_bytes and img_bytes != last_image:
|
||||
write(img_bytes)
|
||||
yield from write(img_bytes)
|
||||
|
||||
# Chrome seems to always ignore first picture,
|
||||
# print it twice.
|
||||
if last_image is None:
|
||||
write(img_bytes)
|
||||
yield from write(img_bytes)
|
||||
|
||||
last_image = img_bytes
|
||||
yield from response.drain()
|
||||
|
||||
yield from asyncio.sleep(.5)
|
||||
|
||||
@@ -358,14 +357,14 @@ class CameraView(HomeAssistantView):
|
||||
|
||||
requires_auth = False
|
||||
|
||||
def __init__(self, entities):
|
||||
def __init__(self, component):
|
||||
"""Initialize a basic camera view."""
|
||||
self.entities = entities
|
||||
self.component = component
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request, entity_id):
|
||||
"""Start a GET request."""
|
||||
camera = self.entities.get(entity_id)
|
||||
camera = self.component.get_entity(entity_id)
|
||||
|
||||
if camera is None:
|
||||
status = 404 if request[KEY_AUTHENTICATED] else 401
|
||||
|
||||
@@ -22,7 +22,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discoveryy_info=None):
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Abode camera devices."""
|
||||
import abodepy.helpers.constants as CONST
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
|
||||
@@ -75,7 +75,9 @@ class ArloCam(Camera):
|
||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
|
||||
self._last_refresh = None
|
||||
self._camera.base_station.refresh_rate = SCAN_INTERVAL.total_seconds()
|
||||
if self._camera.base_station:
|
||||
self._camera.base_station.refresh_rate = \
|
||||
SCAN_INTERVAL.total_seconds()
|
||||
self.attrs = {}
|
||||
|
||||
def camera_image(self):
|
||||
|
||||
76
homeassistant/components/camera/august.py
Normal file
76
homeassistant/components/camera/august.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Support for August camera.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.august/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.components.august import DATA_AUGUST, DEFAULT_TIMEOUT
|
||||
from homeassistant.components.camera import Camera
|
||||
|
||||
DEPENDENCIES = ['august']
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up August cameras."""
|
||||
data = hass.data[DATA_AUGUST]
|
||||
devices = []
|
||||
|
||||
for doorbell in data.doorbells:
|
||||
devices.append(AugustCamera(data, doorbell, DEFAULT_TIMEOUT))
|
||||
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class AugustCamera(Camera):
|
||||
"""An implementation of a Canary security camera."""
|
||||
|
||||
def __init__(self, data, doorbell, timeout):
|
||||
"""Initialize a Canary security camera."""
|
||||
super().__init__()
|
||||
self._data = data
|
||||
self._doorbell = doorbell
|
||||
self._timeout = timeout
|
||||
self._image_url = None
|
||||
self._image_content = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this device."""
|
||||
return self._doorbell.device_name
|
||||
|
||||
@property
|
||||
def is_recording(self):
|
||||
"""Return true if the device is recording."""
|
||||
return self._doorbell.has_subscription
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Return the camera motion detection status."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def brand(self):
|
||||
"""Return the camera brand."""
|
||||
return 'August'
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""Return the camera model."""
|
||||
return 'Doorbell'
|
||||
|
||||
def camera_image(self):
|
||||
"""Return bytes of camera image."""
|
||||
latest = self._data.get_doorbell_detail(self._doorbell.device_id)
|
||||
|
||||
if self._image_url is not latest.image_url:
|
||||
self._image_url = latest.image_url
|
||||
self._image_content = requests.get(self._image_url,
|
||||
timeout=self._timeout).content
|
||||
|
||||
return self._image_content
|
||||
@@ -6,11 +6,11 @@ https://home-assistant.io/components/camera.axis/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
|
||||
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
|
||||
from homeassistant.components.camera.mjpeg import (
|
||||
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
|
||||
from homeassistant.const import (
|
||||
CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
|
||||
CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
|
||||
from homeassistant.helpers.dispatcher import dispatcher_connect
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -20,6 +20,7 @@ DEPENDENCIES = [DOMAIN]
|
||||
|
||||
|
||||
def _get_image_url(host, port, mode):
|
||||
"""Set the URL to get the image."""
|
||||
if mode == 'mjpeg':
|
||||
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
|
||||
elif mode == 'single':
|
||||
@@ -27,34 +28,32 @@ def _get_image_url(host, port, mode):
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Axis camera."""
|
||||
"""Set up the Axis camera."""
|
||||
camera_config = {
|
||||
CONF_NAME: discovery_info[CONF_NAME],
|
||||
CONF_USERNAME: discovery_info[CONF_USERNAME],
|
||||
CONF_PASSWORD: discovery_info[CONF_PASSWORD],
|
||||
CONF_MJPEG_URL: _get_image_url(discovery_info[CONF_HOST],
|
||||
str(discovery_info[CONF_PORT]),
|
||||
'mjpeg'),
|
||||
CONF_STILL_IMAGE_URL: _get_image_url(discovery_info[CONF_HOST],
|
||||
str(discovery_info[CONF_PORT]),
|
||||
'single'),
|
||||
CONF_MJPEG_URL: _get_image_url(
|
||||
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
|
||||
'mjpeg'),
|
||||
CONF_STILL_IMAGE_URL: _get_image_url(
|
||||
discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]),
|
||||
'single'),
|
||||
CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION,
|
||||
}
|
||||
add_devices([AxisCamera(hass,
|
||||
camera_config,
|
||||
str(discovery_info[CONF_PORT]))])
|
||||
add_devices([AxisCamera(
|
||||
hass, camera_config, str(discovery_info[CONF_PORT]))])
|
||||
|
||||
|
||||
class AxisCamera(MjpegCamera):
|
||||
"""AxisCamera class."""
|
||||
"""Representation of a Axis camera."""
|
||||
|
||||
def __init__(self, hass, config, port):
|
||||
"""Initialize Axis Communications camera component."""
|
||||
super().__init__(hass, config)
|
||||
self.port = port
|
||||
dispatcher_connect(hass,
|
||||
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
|
||||
self._new_ip)
|
||||
dispatcher_connect(
|
||||
hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip)
|
||||
|
||||
def _new_ip(self, host):
|
||||
"""Set new IP for video stream."""
|
||||
|
||||
@@ -4,21 +4,21 @@ Support for Blink system camera.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.blink/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
import requests
|
||||
|
||||
from homeassistant.components.blink import DOMAIN
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a Blink Camera."""
|
||||
@@ -45,7 +45,7 @@ class BlinkCamera(Camera):
|
||||
self.notifications = self.data.cameras[self._name].notifications
|
||||
self.response = None
|
||||
|
||||
_LOGGER.info("Initialized blink camera %s", self._name)
|
||||
_LOGGER.debug("Initialized blink camera %s", self._name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -55,7 +55,7 @@ class BlinkCamera(Camera):
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def request_image(self):
|
||||
"""Request a new image from Blink servers."""
|
||||
_LOGGER.info("Requesting new image from blink servers")
|
||||
_LOGGER.debug("Requesting new image from blink servers")
|
||||
image_url = self.check_for_motion()
|
||||
header = self.data.cameras[self._name].header
|
||||
self.response = requests.get(image_url, headers=header, stream=True)
|
||||
@@ -68,7 +68,7 @@ class BlinkCamera(Camera):
|
||||
# We detected motion at some point
|
||||
self.data.last_motion()
|
||||
self.notifications = notifs
|
||||
# returning motion image currently not working
|
||||
# Returning motion image currently not working
|
||||
# return self.data.cameras[self._name].motion['image']
|
||||
elif notifs < self.notifications:
|
||||
self.notifications = notifs
|
||||
|
||||
@@ -4,19 +4,30 @@ Support for Canary camera.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.canary/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
from homeassistant.components.canary import DATA_CANARY, DEFAULT_TIMEOUT
|
||||
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
DEPENDENCIES = ['canary']
|
||||
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
|
||||
|
||||
DEPENDENCIES = ['canary', 'ffmpeg']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_MOTION_START_TIME = "motion_start_time"
|
||||
ATTR_MOTION_END_TIME = "motion_end_time"
|
||||
MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
@@ -25,10 +36,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
devices = []
|
||||
|
||||
for location in data.locations:
|
||||
entries = data.get_motion_entries(location.location_id)
|
||||
if entries:
|
||||
devices.append(CanaryCamera(data, location.location_id,
|
||||
DEFAULT_TIMEOUT))
|
||||
for device in location.devices:
|
||||
if device.is_online:
|
||||
devices.append(
|
||||
CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT,
|
||||
config.get(CONF_FFMPEG_ARGUMENTS)))
|
||||
|
||||
add_devices(devices, True)
|
||||
|
||||
@@ -36,60 +48,65 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class CanaryCamera(Camera):
|
||||
"""An implementation of a Canary security camera."""
|
||||
|
||||
def __init__(self, data, location_id, timeout):
|
||||
def __init__(self, hass, data, location, device, timeout, ffmpeg_args):
|
||||
"""Initialize a Canary security camera."""
|
||||
super().__init__()
|
||||
|
||||
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||
self._ffmpeg_arguments = ffmpeg_args
|
||||
self._data = data
|
||||
self._location_id = location_id
|
||||
self._location = location
|
||||
self._device = device
|
||||
self._timeout = timeout
|
||||
|
||||
self._location = None
|
||||
self._motion_entry = None
|
||||
self._image_content = None
|
||||
|
||||
def camera_image(self):
|
||||
"""Update the status of the camera and return bytes of camera image."""
|
||||
self.update()
|
||||
return self._image_content
|
||||
self._live_stream_session = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this device."""
|
||||
return self._location.name
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def is_recording(self):
|
||||
"""Return true if the device is recording."""
|
||||
return self._location.is_recording
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return device specific state attributes."""
|
||||
if self._motion_entry is None:
|
||||
return None
|
||||
|
||||
return {
|
||||
ATTR_MOTION_START_TIME: self._motion_entry.start_time,
|
||||
ATTR_MOTION_END_TIME: self._motion_entry.end_time,
|
||||
}
|
||||
|
||||
def update(self):
|
||||
"""Update the status of the camera."""
|
||||
self._data.update()
|
||||
self._location = self._data.get_location(self._location_id)
|
||||
|
||||
entries = self._data.get_motion_entries(self._location_id)
|
||||
if entries:
|
||||
current = entries[0]
|
||||
previous = self._motion_entry
|
||||
|
||||
if previous is None or previous.entry_id != current.entry_id:
|
||||
self._motion_entry = current
|
||||
self._image_content = requests.get(
|
||||
current.thumbnails[0].image_url,
|
||||
timeout=self._timeout).content
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Return the camera motion detection status."""
|
||||
return not self._location.is_recording
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
self.renew_live_stream_session()
|
||||
|
||||
from haffmpeg import ImageFrame, IMAGE_JPEG
|
||||
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
image = yield from asyncio.shield(ffmpeg.get_image(
|
||||
self._live_stream_session.live_stream_url,
|
||||
output_format=IMAGE_JPEG,
|
||||
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
|
||||
return image
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_async_mjpeg_stream(self, request):
|
||||
"""Generate an HTTP MJPEG stream from the camera."""
|
||||
if self._live_stream_session is None:
|
||||
return
|
||||
|
||||
from haffmpeg import CameraMjpeg
|
||||
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
yield from stream.open_camera(
|
||||
self._live_stream_session.live_stream_url,
|
||||
extra_cmd=self._ffmpeg_arguments)
|
||||
|
||||
yield from async_aiohttp_proxy_stream(
|
||||
self.hass, request, stream,
|
||||
'multipart/x-mixed-replace;boundary=ffserver')
|
||||
yield from stream.close()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
|
||||
def renew_live_stream_session(self):
|
||||
"""Renew live stream session."""
|
||||
self._live_stream_session = self._data.get_live_stream_session(
|
||||
self._device)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user