mirror of
https://github.com/home-assistant/core.git
synced 2025-09-20 10:29:26 +00:00
Compare commits
1263 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
50e5032f86 | ||
![]() |
1d615ea6c3 | ||
![]() |
2e1b1635b1 | ||
![]() |
fe7dca5144 | ||
![]() |
fdeef2f707 | ||
![]() |
2ec0d25a38 | ||
![]() |
fb5019e73f | ||
![]() |
1e276a7b07 | ||
![]() |
d72a181e30 | ||
![]() |
698d133455 | ||
![]() |
0dccef4063 | ||
![]() |
feb85b90b4 | ||
![]() |
48909539be | ||
![]() |
2355216f61 | ||
![]() |
55a44b0a1c | ||
![]() |
27b0d648a6 | ||
![]() |
90724847a3 | ||
![]() |
90fb33f610 | ||
![]() |
cb59b3fee1 | ||
![]() |
90689c38f7 | ||
![]() |
fd6fd765b2 | ||
![]() |
06a20d0d15 | ||
![]() |
252aea37d2 | ||
![]() |
5a3a43cd5b | ||
![]() |
dd0ca0adc4 | ||
![]() |
c77d2ea341 | ||
![]() |
4a3be6d514 | ||
![]() |
da2cb8e97e | ||
![]() |
42fcaf9a75 | ||
![]() |
af8aec001c | ||
![]() |
f6c5e5ff00 | ||
![]() |
398735c9be | ||
![]() |
8ceeee032c | ||
![]() |
54f01f3f11 | ||
![]() |
bc549e9525 | ||
![]() |
4bb78097a7 | ||
![]() |
7c380588a0 | ||
![]() |
f7daefd7a5 | ||
![]() |
97e6a69adb | ||
![]() |
fe7384a4ef | ||
![]() |
3c9e09ce16 | ||
![]() |
c3d548a0dd | ||
![]() |
ebd64cded9 | ||
![]() |
fee89d8d16 | ||
![]() |
b3d16e8f89 | ||
![]() |
c059dfdb67 | ||
![]() |
d153ee0b9f | ||
![]() |
0f9ae8827c | ||
![]() |
5d52993231 | ||
![]() |
84025e46ff | ||
![]() |
bf66019c66 | ||
![]() |
a748b5ee5e | ||
![]() |
98370560e1 | ||
![]() |
597f53ae30 | ||
![]() |
7ac1e469b7 | ||
![]() |
ecc249aa27 | ||
![]() |
6215e27de4 | ||
![]() |
b282167f26 | ||
![]() |
c278209c7b | ||
![]() |
427d7ee1fc | ||
![]() |
55234a7fa3 | ||
![]() |
3765f882c7 | ||
![]() |
b75ce4f1b2 | ||
![]() |
95663f8126 | ||
![]() |
f114263845 | ||
![]() |
e7ce110dc6 | ||
![]() |
3342db33e4 | ||
![]() |
0fb281c5b3 | ||
![]() |
2dab239021 | ||
![]() |
95c57412ff | ||
![]() |
eb42d59210 | ||
![]() |
6507cc1dc8 | ||
![]() |
1892eb654f | ||
![]() |
5309006494 | ||
![]() |
e2920ce5e5 | ||
![]() |
19d1d748d4 | ||
![]() |
8d661f8dea | ||
![]() |
9363b189ba | ||
![]() |
4da876e5c2 | ||
![]() |
335008ae5c | ||
![]() |
a4da31b573 | ||
![]() |
cd795489ca | ||
![]() |
a8a037db49 | ||
![]() |
fc8e8e5d8c | ||
![]() |
56597d290c | ||
![]() |
8fcec03adf | ||
![]() |
a0ddb24245 | ||
![]() |
23273d3e88 | ||
![]() |
74adebc2fd | ||
![]() |
4b3a932d88 | ||
![]() |
cbe5225e04 | ||
![]() |
811fdc5533 | ||
![]() |
c92e5c147a | ||
![]() |
73d6227021 | ||
![]() |
79f45b5176 | ||
![]() |
b18679ec0b | ||
![]() |
7d566c2c3d | ||
![]() |
46d9d77d03 | ||
![]() |
4a98b32a03 | ||
![]() |
adbcbe3a67 | ||
![]() |
eef3dda1e9 | ||
![]() |
08899ade00 | ||
![]() |
5814fdadd0 | ||
![]() |
daf7d9ea7f | ||
![]() |
956543ae1e | ||
![]() |
fbb6782081 | ||
![]() |
369caeedbd | ||
![]() |
489a02b2c2 | ||
![]() |
c4550d02c5 | ||
![]() |
49733b7fdf | ||
![]() |
0999e2ddc4 | ||
![]() |
d427063acd | ||
![]() |
ff3a4637a4 | ||
![]() |
8523aaca64 | ||
![]() |
e3236d1a3b | ||
![]() |
d7e8616651 | ||
![]() |
c0663bf722 | ||
![]() |
d195fd47f7 | ||
![]() |
e84ff61d4a | ||
![]() |
317bc10ccb | ||
![]() |
1cb42087f9 | ||
![]() |
b035577cf5 | ||
![]() |
55c84eaee3 | ||
![]() |
eb6017e16c | ||
![]() |
19ee3c42b6 | ||
![]() |
af70054692 | ||
![]() |
be94f6e939 | ||
![]() |
f513f6271e | ||
![]() |
588b36dff2 | ||
![]() |
cc5893ed8b | ||
![]() |
124a6cc8c0 | ||
![]() |
0fe4245620 | ||
![]() |
289c88ff71 | ||
![]() |
57f3bed465 | ||
![]() |
62e86270e6 | ||
![]() |
3aceca9d8a | ||
![]() |
e81b3f7bc0 | ||
![]() |
cc6c2bf25e | ||
![]() |
9575cbde09 | ||
![]() |
4e79517971 | ||
![]() |
4ec4cfc44e | ||
![]() |
d74f4eaf52 | ||
![]() |
5696e38dd6 | ||
![]() |
c6aaacbb08 | ||
![]() |
d8ca04a4bc | ||
![]() |
ac9c1235bb | ||
![]() |
c49cce7243 | ||
![]() |
99a20c845c | ||
![]() |
3723f67dc1 | ||
![]() |
b655fe6e04 | ||
![]() |
24e9fa238a | ||
![]() |
83afd12807 | ||
![]() |
82a7dffc03 | ||
![]() |
c11b6798dc | ||
![]() |
8e4c799ad1 | ||
![]() |
5059d4c54b | ||
![]() |
569d9764ab | ||
![]() |
058deb5be3 | ||
![]() |
cd36a71f64 | ||
![]() |
6832a2e642 | ||
![]() |
2c7b2fe19e | ||
![]() |
45ec7f6180 | ||
![]() |
cb8517834a | ||
![]() |
f41ef5d727 | ||
![]() |
a221b10694 | ||
![]() |
6e1785173f | ||
![]() |
52cff83267 | ||
![]() |
e49b970665 | ||
![]() |
a0530d8b9c | ||
![]() |
99d4021f47 | ||
![]() |
7f0d0607f1 | ||
![]() |
cf298c2435 | ||
![]() |
77cdc833f0 | ||
![]() |
96f8c37dcd | ||
![]() |
5b4e30cde3 | ||
![]() |
d4dfb4d80c | ||
![]() |
c895f1f1db | ||
![]() |
944af9cd7d | ||
![]() |
f3e16ca304 | ||
![]() |
6de38cb941 | ||
![]() |
8e51e66c9b | ||
![]() |
57dfe378a1 | ||
![]() |
d8cded637c | ||
![]() |
7c92f7e1ad | ||
![]() |
ccf0559059 | ||
![]() |
2d38e70268 | ||
![]() |
9dae1ca5c2 | ||
![]() |
6ac8caa857 | ||
![]() |
39131d06ba | ||
![]() |
8a626e1572 | ||
![]() |
bc376f7045 | ||
![]() |
cad1de790e | ||
![]() |
32b7f4d16f | ||
![]() |
1adb5040e7 | ||
![]() |
47dad547eb | ||
![]() |
86c06ad76e | ||
![]() |
7dbcf63543 | ||
![]() |
6ff340492b | ||
![]() |
50cd6c9a9c | ||
![]() |
365f21b209 | ||
![]() |
075422e7ad | ||
![]() |
342ec8ec99 | ||
![]() |
2b59b917c4 | ||
![]() |
e40388e7ad | ||
![]() |
cb292a0b18 | ||
![]() |
33663f9502 | ||
![]() |
e57d6f679a | ||
![]() |
775185896a | ||
![]() |
e6331aafb2 | ||
![]() |
fbb4c43353 | ||
![]() |
455ac9724a | ||
![]() |
e6be560e00 | ||
![]() |
59891fa838 | ||
![]() |
475ab68853 | ||
![]() |
d3f8ad15a4 | ||
![]() |
c45fc84859 | ||
![]() |
2a09ac017f | ||
![]() |
f9e8d4237d | ||
![]() |
30e16c97fc | ||
![]() |
0e1f664102 | ||
![]() |
60ca79ce35 | ||
![]() |
f576b37e9f | ||
![]() |
592f9901f9 | ||
![]() |
7991e2df5f | ||
![]() |
91b062f9b7 | ||
![]() |
9919eec596 | ||
![]() |
e525d13a5d | ||
![]() |
ce67be2fff | ||
![]() |
53048f71a0 | ||
![]() |
164e953e8c | ||
![]() |
7156e4782e | ||
![]() |
37fef4016e | ||
![]() |
cee49f313f | ||
![]() |
05330ac763 | ||
![]() |
6884965c80 | ||
![]() |
e992527c68 | ||
![]() |
431a381c8d | ||
![]() |
22088d192a | ||
![]() |
418a8bab11 | ||
![]() |
a94e7ec25d | ||
![]() |
8ac63fd70c | ||
![]() |
8ba9e8016b | ||
![]() |
750ea44b4b | ||
![]() |
5876d6766d | ||
![]() |
78428b0acd | ||
![]() |
72db28abac | ||
![]() |
e13fd05e7d | ||
![]() |
80ab02c3e8 | ||
![]() |
a760673ad6 | ||
![]() |
0bde0a6f3a | ||
![]() |
12dec93565 | ||
![]() |
c376bc2e45 | ||
![]() |
f0e5f68865 | ||
![]() |
56f4486e0b | ||
![]() |
d1b73a96f4 | ||
![]() |
1749859cdf | ||
![]() |
931f4d8161 | ||
![]() |
61508deed3 | ||
![]() |
828c469ef7 | ||
![]() |
1b57566e8e | ||
![]() |
0a6d519b9d | ||
![]() |
0c97fe7eac | ||
![]() |
e8ce41874c | ||
![]() |
0ab0e35d59 | ||
![]() |
51108b8fe9 | ||
![]() |
9e6817b6d0 | ||
![]() |
74581b57f8 | ||
![]() |
4fcaea23a8 | ||
![]() |
b59c29943b | ||
![]() |
1e8c00ac02 | ||
![]() |
9d5c61b2f0 | ||
![]() |
f5eeb252a7 | ||
![]() |
3b4ea864a1 | ||
![]() |
3318f02664 | ||
![]() |
438edc5ca1 | ||
![]() |
abcfcdd887 | ||
![]() |
fff269e790 | ||
![]() |
81a27e726c | ||
![]() |
7c120748ce | ||
![]() |
e83816c055 | ||
![]() |
cd2703e121 | ||
![]() |
c2828bac2c | ||
![]() |
ad7370e1c2 | ||
![]() |
3b7f16f189 | ||
![]() |
cc03f7ee6a | ||
![]() |
ecc1429453 | ||
![]() |
98568b5eb7 | ||
![]() |
9d9ca64f26 | ||
![]() |
1d31137616 | ||
![]() |
f86bd15580 | ||
![]() |
cbf65220aa | ||
![]() |
c100b8cb52 | ||
![]() |
654ad41464 | ||
![]() |
a2abb4ae0a | ||
![]() |
36e266442f | ||
![]() |
f3d9086ff4 | ||
![]() |
0c09cfc6c4 | ||
![]() |
b0b6026c68 | ||
![]() |
8f47a9109c | ||
![]() |
f0293eeac2 | ||
![]() |
e4317a6741 | ||
![]() |
4b449f5f93 | ||
![]() |
8760dc9b29 | ||
![]() |
1831a7da68 | ||
![]() |
3e34f34f6b | ||
![]() |
3fec2955a5 | ||
![]() |
2cf9254a08 | ||
![]() |
333da0dc6d | ||
![]() |
7b10f0a14f | ||
![]() |
fb6bdfaba9 | ||
![]() |
d7da90ae54 | ||
![]() |
a5bfcceacd | ||
![]() |
4961ece931 | ||
![]() |
7d99d6aad9 | ||
![]() |
6dc93c2751 | ||
![]() |
5c39eebea8 | ||
![]() |
ffd295b38b | ||
![]() |
5d810dae86 | ||
![]() |
486bcc4cae | ||
![]() |
cc2de5e1dc | ||
![]() |
77d8e393a1 | ||
![]() |
c6bf529d38 | ||
![]() |
dac9716cf4 | ||
![]() |
9043895407 | ||
![]() |
2f08a91fdd | ||
![]() |
1807b45222 | ||
![]() |
b4f392b181 | ||
![]() |
8e8ec7a7c3 | ||
![]() |
7edf14e55f | ||
![]() |
7bea69ce83 | ||
![]() |
8d31c5fbf6 | ||
![]() |
dc42b6358a | ||
![]() |
06ceadfd54 | ||
![]() |
4359e0babf | ||
![]() |
ee153062ab | ||
![]() |
fada6d3f49 | ||
![]() |
f6a5e0887d | ||
![]() |
4f8d2ec317 | ||
![]() |
e63a96cf53 | ||
![]() |
a5c0831dc1 | ||
![]() |
718949481f | ||
![]() |
90639d33ab | ||
![]() |
966809c1a1 | ||
![]() |
bc27d173d0 | ||
![]() |
fde291f866 | ||
![]() |
49c399c358 | ||
![]() |
8d1999dc12 | ||
![]() |
ee05a4ab89 | ||
![]() |
8a42e1551a | ||
![]() |
9cc3e7e47b | ||
![]() |
54755df9ea | ||
![]() |
84ebcd8a59 | ||
![]() |
f1280d3edb | ||
![]() |
c27074e6f7 | ||
![]() |
c8bfcd2ed4 | ||
![]() |
42699b7a60 | ||
![]() |
6bc07298d3 | ||
![]() |
4ece4bf241 | ||
![]() |
1a86fa5a02 | ||
![]() |
d54a634f11 | ||
![]() |
5e1ff20b09 | ||
![]() |
29266213a0 | ||
![]() |
2aa89cfe07 | ||
![]() |
879c816f5c | ||
![]() |
4ae11c009d | ||
![]() |
dcd6f7a29e | ||
![]() |
fde4a7d029 | ||
![]() |
b83ff739bc | ||
![]() |
8c9b3898fc | ||
![]() |
95e0027924 | ||
![]() |
c67c20f752 | ||
![]() |
1a1571cd52 | ||
![]() |
cca0d3ed44 | ||
![]() |
f0479855bd | ||
![]() |
40aafcdf5d | ||
![]() |
8c9557401f | ||
![]() |
ffd3081743 | ||
![]() |
d0275c8075 | ||
![]() |
f6c3832e90 | ||
![]() |
d29bdddaa7 | ||
![]() |
d3be056d15 | ||
![]() |
bffa0d2b04 | ||
![]() |
23b65bfb30 | ||
![]() |
1d4a7f1160 | ||
![]() |
dc08852fc2 | ||
![]() |
3377f30613 | ||
![]() |
84ca4d2a21 | ||
![]() |
1366c93c83 | ||
![]() |
e5e2a151aa | ||
![]() |
bd1e533409 | ||
![]() |
21e82bd037 | ||
![]() |
af9a0e8fea | ||
![]() |
abc5c3e128 | ||
![]() |
543e8bb62e | ||
![]() |
6ca828fd14 | ||
![]() |
87b83f3602 | ||
![]() |
5829cdfdf1 | ||
![]() |
d473f3407b | ||
![]() |
9373d5e901 | ||
![]() |
d8abef9210 | ||
![]() |
4fde0ffe9c | ||
![]() |
ba019c799a | ||
![]() |
5581c6295e | ||
![]() |
192db5bec3 | ||
![]() |
b8eaec565a | ||
![]() |
e0f35c0279 | ||
![]() |
2eeeb9075a | ||
![]() |
71ee290bfd | ||
![]() |
7aad93e90d | ||
![]() |
a65f22378e | ||
![]() |
bb9db28c95 | ||
![]() |
d10f017441 | ||
![]() |
b6e0286d71 | ||
![]() |
4451d2e847 | ||
![]() |
229000b834 | ||
![]() |
9704057959 | ||
![]() |
effb9e9d23 | ||
![]() |
effbb3bd4c | ||
![]() |
471501d386 | ||
![]() |
ef94b5c77a | ||
![]() |
60dcc9a5c0 | ||
![]() |
5b4862cc3c | ||
![]() |
fbf945c18b | ||
![]() |
609c25691a | ||
![]() |
6e77877743 | ||
![]() |
7b105a2150 | ||
![]() |
ee57a823af | ||
![]() |
04b1621b65 | ||
![]() |
f5e24cb0bb | ||
![]() |
ac72dea09a | ||
![]() |
2f474a0ed8 | ||
![]() |
7a4cc8e082 | ||
![]() |
92dc76773a | ||
![]() |
fe4abc8454 | ||
![]() |
821d01f82c | ||
![]() |
b453834b2f | ||
![]() |
97f14015ea | ||
![]() |
4fb25cf16d | ||
![]() |
e7b5c5812c | ||
![]() |
2ac423bd9d | ||
![]() |
ec7ca9a560 | ||
![]() |
cb298123d4 | ||
![]() |
c5bf4fe339 | ||
![]() |
57c5ed33ee | ||
![]() |
3be0103259 | ||
![]() |
614b5da170 | ||
![]() |
acf6d4ab82 | ||
![]() |
d3acb25070 | ||
![]() |
222ad3ab6d | ||
![]() |
5ae2bcdbb7 | ||
![]() |
6c9742afc4 | ||
![]() |
cf924cd14d | ||
![]() |
f2267437df | ||
![]() |
233920f22c | ||
![]() |
7536e825fa | ||
![]() |
e12a9eaadd | ||
![]() |
fb184b4b6f | ||
![]() |
63ff173305 | ||
![]() |
903e6b5aee | ||
![]() |
46ce26eb7a | ||
![]() |
b1bba3675d | ||
![]() |
ed5d10448e | ||
![]() |
652c006cbc | ||
![]() |
b67c5df525 | ||
![]() |
a7d5a8d93e | ||
![]() |
c48c2b00a8 | ||
![]() |
9bc5cd2d4b | ||
![]() |
ecf3a9cb36 | ||
![]() |
074e31bcf9 | ||
![]() |
63cc658010 | ||
![]() |
8682f21fc5 | ||
![]() |
aa28e6727d | ||
![]() |
12129f0e6a | ||
![]() |
8a7cfce67b | ||
![]() |
5e71e9b826 | ||
![]() |
db8bb53984 | ||
![]() |
692f4c293b | ||
![]() |
da37380410 | ||
![]() |
fa4aa2244e | ||
![]() |
c63bdd5afe | ||
![]() |
20a9899354 | ||
![]() |
fe6a4b8ae5 | ||
![]() |
143044f8f1 | ||
![]() |
d655c0e358 | ||
![]() |
46e030662d | ||
![]() |
5779d64e98 | ||
![]() |
83a5f932d1 | ||
![]() |
a12fa2e5bf | ||
![]() |
ee37fc344b | ||
![]() |
8cc0748db3 | ||
![]() |
0ecceb601b | ||
![]() |
2a1a5e53a1 | ||
![]() |
c8b782189e | ||
![]() |
74016c4179 | ||
![]() |
c30c8df449 | ||
![]() |
58de661ad5 | ||
![]() |
f4a97db783 | ||
![]() |
b220ceec9c | ||
![]() |
fb796b5481 | ||
![]() |
ea5bec3ef4 | ||
![]() |
8185587100 | ||
![]() |
061a38cc3b | ||
![]() |
23fc5e2c9f | ||
![]() |
6496c38ce6 | ||
![]() |
3363b88a73 | ||
![]() |
2e17d0926a | ||
![]() |
85ac50cc77 | ||
![]() |
da61b18392 | ||
![]() |
8a88af20da | ||
![]() |
f8527e9773 | ||
![]() |
7977996c0d | ||
![]() |
22681fbe08 | ||
![]() |
1e655eea74 | ||
![]() |
8d940fb585 | ||
![]() |
afe3dd8dbb | ||
![]() |
bf96f28e95 | ||
![]() |
5cba3085b4 | ||
![]() |
407a419c83 | ||
![]() |
4ab778fd97 | ||
![]() |
ee7d4710c4 | ||
![]() |
3a6434f566 | ||
![]() |
a2f5b630d6 | ||
![]() |
3f2fa0ed5a | ||
![]() |
865865ca0f | ||
![]() |
05ced33648 | ||
![]() |
b4165fe9f3 | ||
![]() |
47aa8c387a | ||
![]() |
2b94857ffd | ||
![]() |
7461c57542 | ||
![]() |
632f9a21b6 | ||
![]() |
da44f80b32 | ||
![]() |
0bf5021c2c | ||
![]() |
8fb49e8687 | ||
![]() |
b82003ae08 | ||
![]() |
5f8dc8af20 | ||
![]() |
c13fdd23c1 | ||
![]() |
e6e0e5263a | ||
![]() |
0981956caa | ||
![]() |
d267fc608f | ||
![]() |
e077998d38 | ||
![]() |
d3bc8519c0 | ||
![]() |
d3adc6ddfb | ||
![]() |
a3f586d097 | ||
![]() |
f8c7fd212f | ||
![]() |
b1f3492fd0 | ||
![]() |
7123ec14be | ||
![]() |
8e4394f173 | ||
![]() |
5e56bc7464 | ||
![]() |
ed20f7e359 | ||
![]() |
74acc5cf41 | ||
![]() |
0bcb7839fb | ||
![]() |
17237e9d3f | ||
![]() |
a663dbada0 | ||
![]() |
96e1d5524a | ||
![]() |
33fd2250fd | ||
![]() |
31f17a91e6 | ||
![]() |
d0720ac699 | ||
![]() |
05acf1c10a | ||
![]() |
27c92937f2 | ||
![]() |
a328df6014 | ||
![]() |
1fb4eefc2c | ||
![]() |
0f12b4c955 | ||
![]() |
a9f14b67a8 | ||
![]() |
445065700c | ||
![]() |
4bd96fd437 | ||
![]() |
5dde0c2201 | ||
![]() |
6846a76c46 | ||
![]() |
fa6e93f0c7 | ||
![]() |
5ef274adce | ||
![]() |
e39f7d3ef5 | ||
![]() |
88b9503962 | ||
![]() |
596093d564 | ||
![]() |
23400c4b0a | ||
![]() |
af54311718 | ||
![]() |
442dcd584b | ||
![]() |
1e4aec63ed | ||
![]() |
80c187f8ea | ||
![]() |
d73b695e73 | ||
![]() |
f02d169864 | ||
![]() |
2dd7f0616e | ||
![]() |
2f2952e0ec | ||
![]() |
8358542ce0 | ||
![]() |
4ca5ed25bc | ||
![]() |
7bf6ceafec | ||
![]() |
1cfed4f015 | ||
![]() |
a082ffca1d | ||
![]() |
1b563b0640 | ||
![]() |
1fe189e9cb | ||
![]() |
edeb92ea42 | ||
![]() |
c1095665e9 | ||
![]() |
2a1f8af10a | ||
![]() |
6234f2d73f | ||
![]() |
b488663f2c | ||
![]() |
a55d8776ff | ||
![]() |
5ceb4c404d | ||
![]() |
0061cece0c | ||
![]() |
0099168ff8 | ||
![]() |
87c89752ab | ||
![]() |
45f6f4443a | ||
![]() |
f1290d3135 | ||
![]() |
746aae51ec | ||
![]() |
da9430ed12 | ||
![]() |
bef22076ea | ||
![]() |
fe93b51017 | ||
![]() |
07293e8d1e | ||
![]() |
ca71d34076 | ||
![]() |
548417761e | ||
![]() |
7b8ad1d365 | ||
![]() |
61cb6ec3dc | ||
![]() |
349746f5f2 | ||
![]() |
2e3b279873 | ||
![]() |
f26861976d | ||
![]() |
6bfeac7f80 | ||
![]() |
a95fe588ca | ||
![]() |
e5d11dd1a5 | ||
![]() |
435e5c8a91 | ||
![]() |
8d0553d9e6 | ||
![]() |
9a239d1afb | ||
![]() |
9252854f99 | ||
![]() |
8d76e2679d | ||
![]() |
4b1dcad7ae | ||
![]() |
b45c386fd6 | ||
![]() |
cb5fa79835 | ||
![]() |
b74217bec2 | ||
![]() |
fcf60e740d | ||
![]() |
bb05600010 | ||
![]() |
d3bb6d3988 | ||
![]() |
f3945147a4 | ||
![]() |
6398e92836 | ||
![]() |
4d2b79156d | ||
![]() |
b6d335f993 | ||
![]() |
4b82c34b8f | ||
![]() |
66fc852363 | ||
![]() |
87274879a8 | ||
![]() |
e4dbf8033c | ||
![]() |
43db94d62d | ||
![]() |
6d5fca2db1 | ||
![]() |
d5e55448ef | ||
![]() |
4ad998378f | ||
![]() |
d46607c0d0 | ||
![]() |
04920fa0bf | ||
![]() |
1928da1fae | ||
![]() |
06b051c53d | ||
![]() |
473d765bb9 | ||
![]() |
8e34c27b63 | ||
![]() |
77aa2e940d | ||
![]() |
3bbaf37193 | ||
![]() |
b2d6ff9783 | ||
![]() |
4fdde4f0e2 | ||
![]() |
756768e745 | ||
![]() |
83b791489b | ||
![]() |
35132f9836 | ||
![]() |
0e08785373 | ||
![]() |
bf0dbdfd6a | ||
![]() |
04407b8623 | ||
![]() |
fb0ee34f10 | ||
![]() |
ef63cfe8e4 | ||
![]() |
e40f72e773 | ||
![]() |
cec8ccb1a4 | ||
![]() |
9b1ed4e79b | ||
![]() |
8fffaebe50 | ||
![]() |
84aab1c973 | ||
![]() |
a2fbc0d2ef | ||
![]() |
6a017efc0e | ||
![]() |
363a429c41 | ||
![]() |
9fc22ee47a | ||
![]() |
a250f583eb | ||
![]() |
bf495edbb5 | ||
![]() |
3ea7dee83d | ||
![]() |
d796e8db5c | ||
![]() |
d24b45054a | ||
![]() |
18935440ed | ||
![]() |
2ba6b3a2ab | ||
![]() |
2438c6b7c2 | ||
![]() |
32a84f1466 | ||
![]() |
0002a895ca | ||
![]() |
d0b43b187a | ||
![]() |
33d381731f | ||
![]() |
18f81d7824 | ||
![]() |
844c8149d7 | ||
![]() |
7617864ba5 | ||
![]() |
58c234466c | ||
![]() |
9071946e87 | ||
![]() |
b24aa24f6a | ||
![]() |
1fde234c78 | ||
![]() |
d67f3b8060 | ||
![]() |
afb9cba806 | ||
![]() |
1c2f4866e2 | ||
![]() |
e90ae2fb75 | ||
![]() |
4339e9aab1 | ||
![]() |
9b640f6a81 | ||
![]() |
437ddb8dea | ||
![]() |
a119bd0056 | ||
![]() |
0eaad46d93 | ||
![]() |
8af6bacfd0 | ||
![]() |
09ca440c20 | ||
![]() |
74cc675a38 | ||
![]() |
c478f2c7d0 | ||
![]() |
a3a702b269 | ||
![]() |
92a6f21cc2 | ||
![]() |
814834512a | ||
![]() |
46f3088a70 | ||
![]() |
deed760008 | ||
![]() |
d1da53615f | ||
![]() |
69c919183a | ||
![]() |
8eb29787a5 | ||
![]() |
ae3973144c | ||
![]() |
02f7eb9675 | ||
![]() |
8c0967a190 | ||
![]() |
bf2fe60cb5 | ||
![]() |
1ddcab5e26 | ||
![]() |
09fec29537 | ||
![]() |
9189cbdc8b | ||
![]() |
7fae8cd0f1 | ||
![]() |
843f8ce9ee | ||
![]() |
2bf781185f | ||
![]() |
1e1d4c2013 | ||
![]() |
bde711a9ff | ||
![]() |
dc45ed38e7 | ||
![]() |
03f916ed10 | ||
![]() |
6e33c12008 | ||
![]() |
401309c3b2 | ||
![]() |
1c06b51968 | ||
![]() |
e7de1fb9ae | ||
![]() |
de0f6b781e | ||
![]() |
314bce1073 | ||
![]() |
9e16be3173 | ||
![]() |
1b1619fbf1 | ||
![]() |
1f226cffe9 | ||
![]() |
b9ee5fb867 | ||
![]() |
ba80d5e52a | ||
![]() |
f2feabcf0b | ||
![]() |
a19e7ba3f1 | ||
![]() |
49d642741d | ||
![]() |
db0efc647d | ||
![]() |
640c692e1f | ||
![]() |
4aef0b68bc | ||
![]() |
c2b7c93375 | ||
![]() |
8cc759ea4b | ||
![]() |
a223efb840 | ||
![]() |
c32807803e | ||
![]() |
24a172163a | ||
![]() |
372169a03a | ||
![]() |
e4d100d54d | ||
![]() |
bfd9623d8b | ||
![]() |
3464454662 | ||
![]() |
533bb5565b | ||
![]() |
a8709a6988 | ||
![]() |
4b767b088e | ||
![]() |
c52b18d7c8 | ||
![]() |
aaaf9637eb | ||
![]() |
055db05946 | ||
![]() |
0863d50210 | ||
![]() |
1e352d37d0 | ||
![]() |
620197b276 | ||
![]() |
727a22f925 | ||
![]() |
9bea7d7d8b | ||
![]() |
97f62cfb78 | ||
![]() |
482db94372 | ||
![]() |
8a4e993183 | ||
![]() |
790610525b | ||
![]() |
7e668ef9e3 | ||
![]() |
4dbf7be267 | ||
![]() |
36eb0ceff3 | ||
![]() |
d38acfbd39 | ||
![]() |
b87e31617a | ||
![]() |
bb6fe822f9 | ||
![]() |
5504a511e3 | ||
![]() |
5c96936eb4 | ||
![]() |
cbbb15fa48 | ||
![]() |
760138ac52 | ||
![]() |
b1f538b622 | ||
![]() |
ac8592587f | ||
![]() |
aee25a020d | ||
![]() |
13df925795 | ||
![]() |
2b850f417e | ||
![]() |
f303f6a191 | ||
![]() |
f8cfa15152 | ||
![]() |
12f731b32c | ||
![]() |
11dcbd4449 | ||
![]() |
fa6a089fb3 | ||
![]() |
87da2ff1d7 | ||
![]() |
b576df53e9 | ||
![]() |
b90964faad | ||
![]() |
549133a062 | ||
![]() |
c29553517f | ||
![]() |
2e27c0d5ec | ||
![]() |
774f584ba8 | ||
![]() |
81b1446aad | ||
![]() |
6bfd52ada8 | ||
![]() |
0646d01152 | ||
![]() |
da5f5335eb | ||
![]() |
c9d55cff23 | ||
![]() |
aeb1d3d3fe | ||
![]() |
a1c119adb6 | ||
![]() |
e9f273e7e0 | ||
![]() |
7ebf36bb70 | ||
![]() |
84fe4f75df | ||
![]() |
c07bf551d9 | ||
![]() |
a745bf83ef | ||
![]() |
1432ae649a | ||
![]() |
cf1a27bd7c | ||
![]() |
3d8b7a4122 | ||
![]() |
e50588afe1 | ||
![]() |
4dc4a98caa | ||
![]() |
423e809e45 | ||
![]() |
a79f1d4d40 | ||
![]() |
8461cf2717 | ||
![]() |
9c9f5068b7 | ||
![]() |
6d41024e76 | ||
![]() |
7d24efc690 | ||
![]() |
7d4adbbef5 | ||
![]() |
e11ec88482 | ||
![]() |
e39bdf8763 | ||
![]() |
a33bcdf270 | ||
![]() |
f056cbc641 | ||
![]() |
4163bcebbc | ||
![]() |
d472d81538 | ||
![]() |
2b70b1881a | ||
![]() |
12607aeaea | ||
![]() |
1855f1ae85 | ||
![]() |
613da308f2 | ||
![]() |
cefacf9ce4 | ||
![]() |
78887c5d5c | ||
![]() |
3a92bd78ea | ||
![]() |
d0021a6171 | ||
![]() |
e2cfdbff06 | ||
![]() |
9480f41210 | ||
![]() |
1b5f6aa1b9 | ||
![]() |
2065426b16 | ||
![]() |
beb8c05d91 | ||
![]() |
cf42303afb | ||
![]() |
4bcbeef480 | ||
![]() |
e0712ba329 | ||
![]() |
66d6f5174d | ||
![]() |
9762e1613d | ||
![]() |
bb92ef5497 | ||
![]() |
9f5bfe28d1 | ||
![]() |
8ee32a8fbd | ||
![]() |
052cd3fc53 | ||
![]() |
0ccaf97924 | ||
![]() |
96b20b3a97 | ||
![]() |
91806bfa2a | ||
![]() |
1c4e097bed | ||
![]() |
2df6aabbf3 | ||
![]() |
81b2111751 | ||
![]() |
f7e0d13fe6 | ||
![]() |
5e5c0daa87 | ||
![]() |
a7277db4d7 | ||
![]() |
ba44b7edb3 | ||
![]() |
8fcc750998 | ||
![]() |
eff619a58f | ||
![]() |
fc1bb58247 | ||
![]() |
c12b8f763c | ||
![]() |
ef51d8518a | ||
![]() |
8b7894fb86 | ||
![]() |
010f098df3 | ||
![]() |
1f3bb51821 | ||
![]() |
10367eb250 | ||
![]() |
7fb5488058 | ||
![]() |
e68bd0457c | ||
![]() |
910020bc5f | ||
![]() |
f43db3c615 | ||
![]() |
9e9705d6b2 | ||
![]() |
6899c7b6f7 | ||
![]() |
d0c9d6b69a | ||
![]() |
81aaeaaf11 | ||
![]() |
65c3201fa6 | ||
![]() |
3a843e1817 | ||
![]() |
0c7f8e910e | ||
![]() |
0abde3aa57 | ||
![]() |
775d45ae5a | ||
![]() |
e7d783ca2a | ||
![]() |
ef4ef2d383 | ||
![]() |
3638b21bcb | ||
![]() |
54c45f80c1 | ||
![]() |
e3307fb1c2 | ||
![]() |
b5f20c9b64 | ||
![]() |
7055fddfb4 | ||
![]() |
fce09f624b | ||
![]() |
be53cc7068 | ||
![]() |
f3dabe21ab | ||
![]() |
228fb8c072 | ||
![]() |
c556b619b7 | ||
![]() |
2682996939 | ||
![]() |
6872daab89 | ||
![]() |
6d183e8bb3 | ||
![]() |
cdc8628e5a | ||
![]() |
dc4b0695b5 | ||
![]() |
3fb691ead6 | ||
![]() |
a9926e355f | ||
![]() |
17cbe0c6ce | ||
![]() |
783abc7996 | ||
![]() |
47355eed41 | ||
![]() |
d5642a5faf | ||
![]() |
ca3f07cdef | ||
![]() |
99ea1e3f4f | ||
![]() |
bb8de5845a | ||
![]() |
b3cb057aac | ||
![]() |
922303fd4b | ||
![]() |
8c1181f8e3 | ||
![]() |
4a0d6e73f4 | ||
![]() |
171086229a | ||
![]() |
927024714b | ||
![]() |
24b7fd3694 | ||
![]() |
d6f43ba839 | ||
![]() |
3492545ec1 | ||
![]() |
ceff9981be | ||
![]() |
70ea16bdc0 | ||
![]() |
943958b140 | ||
![]() |
23c5fc0aad | ||
![]() |
45b4ef46cc | ||
![]() |
44edf3e105 | ||
![]() |
81f0826550 | ||
![]() |
adde9e6231 | ||
![]() |
de85d38aa5 | ||
![]() |
f637a07016 | ||
![]() |
9e153119ef | ||
![]() |
b5c54864ac | ||
![]() |
d369d70ca5 | ||
![]() |
5aa72562a7 | ||
![]() |
c4da921cb5 | ||
![]() |
7daa92249a | ||
![]() |
4a3d9a956d | ||
![]() |
6662b7f52d | ||
![]() |
e91fe94585 | ||
![]() |
88ffe39945 | ||
![]() |
e479324db9 | ||
![]() |
f65cc68705 | ||
![]() |
238921b681 | ||
![]() |
0fd415d7fb | ||
![]() |
0eb6540fe7 | ||
![]() |
fc0c8540d3 | ||
![]() |
eb473600f6 | ||
![]() |
de999d8439 | ||
![]() |
6d97546f40 | ||
![]() |
e773133bcf | ||
![]() |
3d4b2436db | ||
![]() |
a068efcd47 | ||
![]() |
f86edd4f24 | ||
![]() |
a24aebd5ae | ||
![]() |
f3b9e1e988 | ||
![]() |
76b747edd6 | ||
![]() |
f7d25396a4 | ||
![]() |
0e9728d94a | ||
![]() |
3b69de8a1a | ||
![]() |
f0b2a6d0e6 | ||
![]() |
d2ed3a131f | ||
![]() |
ed5f94fd8a | ||
![]() |
9dcc0b5ef5 | ||
![]() |
1fafa34eb1 | ||
![]() |
71ed17b836 | ||
![]() |
a7f933966b | ||
![]() |
641ba014f2 | ||
![]() |
d5ca6a5aed | ||
![]() |
d6081f3dc5 | ||
![]() |
f25347d98d | ||
![]() |
a1dc35fc75 | ||
![]() |
4da91d6a8b | ||
![]() |
5c4a21efac | ||
![]() |
e2e58e6acc | ||
![]() |
d0304198de | ||
![]() |
36d7fe72eb | ||
![]() |
6d245c43fc | ||
![]() |
e1a4d51fa2 | ||
![]() |
352cca1037 | ||
![]() |
206d02d531 | ||
![]() |
cfbbade6d1 | ||
![]() |
cfea4b17e3 | ||
![]() |
9c4bc2a47f | ||
![]() |
4cdf0b4969 | ||
![]() |
ad15844cf4 | ||
![]() |
25cb7c652b | ||
![]() |
189023821b | ||
![]() |
c118be6639 | ||
![]() |
11a3dc268f | ||
![]() |
f0ce6c8210 | ||
![]() |
ed0ec613c3 | ||
![]() |
4a3048b370 | ||
![]() |
fdb7371256 | ||
![]() |
a96a98a260 | ||
![]() |
1ab7103aea | ||
![]() |
416b8e0efe | ||
![]() |
5b3ef0f76f | ||
![]() |
452c3a1b25 | ||
![]() |
8da10f670b | ||
![]() |
b805d8a844 | ||
![]() |
76675a54f8 | ||
![]() |
0e246059f9 | ||
![]() |
0e41342a40 | ||
![]() |
04f1054d07 | ||
![]() |
966bda079e | ||
![]() |
ef4587f994 | ||
![]() |
2c8f6a0ad0 | ||
![]() |
8cdadd2aa0 | ||
![]() |
3bdf77ad62 | ||
![]() |
8c90fd19ff | ||
![]() |
71b4afb780 | ||
![]() |
6e6a000217 | ||
![]() |
85e71fc785 | ||
![]() |
216199556a | ||
![]() |
89d950c73a | ||
![]() |
b30c352e37 | ||
![]() |
1312ee0f7d | ||
![]() |
f4915ddb0b | ||
![]() |
43296069c3 | ||
![]() |
1eaec8f406 | ||
![]() |
5d820ec188 | ||
![]() |
d86dfb6336 | ||
![]() |
b34c58386c | ||
![]() |
5cb3382425 | ||
![]() |
40d27cde0e | ||
![]() |
419d97fc06 | ||
![]() |
1cd51bc6a8 | ||
![]() |
c12c742297 | ||
![]() |
ce879b7eb8 | ||
![]() |
d7e3962cc0 | ||
![]() |
86b34b40a1 | ||
![]() |
12293d6600 | ||
![]() |
66cbdc3043 | ||
![]() |
e1d1385358 | ||
![]() |
bafc04ca42 | ||
![]() |
5717c87097 | ||
![]() |
ab9c394e93 | ||
![]() |
5d13f36a4b | ||
![]() |
4165629f97 | ||
![]() |
f87b9b7b85 | ||
![]() |
2ab45441a8 | ||
![]() |
fcdfebefd9 | ||
![]() |
9b920b3b40 | ||
![]() |
0c94df9fcf | ||
![]() |
1ba4435693 | ||
![]() |
00ec50da4b | ||
![]() |
c1056ea4d4 | ||
![]() |
9440ff881f | ||
![]() |
c525ee9daa | ||
![]() |
79ca47640e | ||
![]() |
41212b90c4 | ||
![]() |
aa6339818e | ||
![]() |
305309a59e | ||
![]() |
ea095de98e | ||
![]() |
e8a33758c1 | ||
![]() |
47034f83f4 | ||
![]() |
2c1df75c07 | ||
![]() |
43ff95ad3b | ||
![]() |
e2559fd6cf | ||
![]() |
abe6f9343f | ||
![]() |
7a70496b11 | ||
![]() |
7dd7f509ca | ||
![]() |
6cc85adb81 | ||
![]() |
2971a24c56 | ||
![]() |
20ded1ba3e | ||
![]() |
2e4ae3e73d | ||
![]() |
4b5be750b2 | ||
![]() |
92411cdc18 | ||
![]() |
94f7c397d7 | ||
![]() |
526abdd329 | ||
![]() |
61196b1c83 | ||
![]() |
03e3fb77c4 | ||
![]() |
bfd6110091 | ||
![]() |
71f9507df7 | ||
![]() |
1c5eb88368 | ||
![]() |
4be89521d4 | ||
![]() |
1e4b56c4d4 | ||
![]() |
655b82c1e2 | ||
![]() |
81b567b68f | ||
![]() |
009a9d59ed | ||
![]() |
629bf3eefd | ||
![]() |
3b237795ba | ||
![]() |
80e55fb286 | ||
![]() |
12910de9ae | ||
![]() |
2f686124c8 | ||
![]() |
b59ca034ae | ||
![]() |
78a3f259d6 | ||
![]() |
1d0acb5a2c | ||
![]() |
ea36c91919 | ||
![]() |
1d9f1487c3 | ||
![]() |
d251621f2b | ||
![]() |
8d50045971 | ||
![]() |
cc0299d046 | ||
![]() |
7e539a3cb2 | ||
![]() |
e3bb45c906 | ||
![]() |
df13352989 | ||
![]() |
05a3c463bf | ||
![]() |
c44ebbefc4 | ||
![]() |
fab533de87 | ||
![]() |
ad5a9bf5ac | ||
![]() |
c5a91393e4 | ||
![]() |
494a776959 | ||
![]() |
193270c4fb | ||
![]() |
ec490070ca | ||
![]() |
8233f086cd | ||
![]() |
99e34539b9 | ||
![]() |
71d909483c | ||
![]() |
dbd6f7e4ed | ||
![]() |
403a721e91 | ||
![]() |
af5439860f | ||
![]() |
0f94c8a2e7 | ||
![]() |
203f48cadc | ||
![]() |
1a74c41056 | ||
![]() |
b321e0ef80 | ||
![]() |
70ad06d71c | ||
![]() |
c35d09d5f0 | ||
![]() |
f389266f5f | ||
![]() |
f9627a5646 | ||
![]() |
517bd39015 | ||
![]() |
11dc7246af | ||
![]() |
8654993b18 | ||
![]() |
1aa3ab52b7 | ||
![]() |
d68f59ca6c | ||
![]() |
4d52b0ecd5 | ||
![]() |
1b2c83145c | ||
![]() |
12c8266942 | ||
![]() |
a4f1f6e724 | ||
![]() |
0e08925259 | ||
![]() |
570c5549a9 | ||
![]() |
f4f06af0c5 | ||
![]() |
752a4b958e | ||
![]() |
350a6fd5fa | ||
![]() |
f17c1090e1 | ||
![]() |
8ea6c7319a | ||
![]() |
da2521a299 | ||
![]() |
098e28534b | ||
![]() |
dc716cd971 | ||
![]() |
7a24e210ae | ||
![]() |
fafd0d4e4c | ||
![]() |
8ba7e61ed6 | ||
![]() |
86cfc2a0ed | ||
![]() |
6aac53399d | ||
![]() |
bc0559813c | ||
![]() |
dd7690f265 | ||
![]() |
b6827ce57a | ||
![]() |
ae93cf702a | ||
![]() |
ecf511ff35 | ||
![]() |
87ef26be22 | ||
![]() |
0fe0f1918a | ||
![]() |
c085f06df5 | ||
![]() |
5d7403bd81 | ||
![]() |
c14b829f27 | ||
![]() |
f20a81d0c5 | ||
![]() |
9afbbbf3fe | ||
![]() |
1f4f2d7086 | ||
![]() |
7ff1ded0b5 | ||
![]() |
8df5de2bb8 | ||
![]() |
955e3e0542 | ||
![]() |
9f68fd9184 | ||
![]() |
85b7433bc4 | ||
![]() |
3ee4d1060f | ||
![]() |
e22e70a01a | ||
![]() |
cf664e42cc | ||
![]() |
b815ccc3b8 | ||
![]() |
7b1e948e8d | ||
![]() |
607394a0a0 | ||
![]() |
e4ebae55d5 | ||
![]() |
ce3d8be72b | ||
![]() |
a1be80d5d4 | ||
![]() |
dc3706523a | ||
![]() |
55731b759f | ||
![]() |
064b2cdb9f | ||
![]() |
64a7be66b1 | ||
![]() |
ae9f44c708 | ||
![]() |
74362df19c | ||
![]() |
d8afea64af | ||
![]() |
b853fb2178 | ||
![]() |
5b22e57643 | ||
![]() |
88782fa90a | ||
![]() |
4c06cca3e1 | ||
![]() |
d3042a8199 | ||
![]() |
e61c1ffbc2 | ||
![]() |
6cfc1b6af8 | ||
![]() |
c120c47bc1 | ||
![]() |
44dbf2678b | ||
![]() |
bbe8b2019b | ||
![]() |
f9c9b3c1b8 | ||
![]() |
9dd28ac18f | ||
![]() |
8114c45b3d | ||
![]() |
89164d0244 | ||
![]() |
79e134b076 | ||
![]() |
630516f309 | ||
![]() |
6902e522b9 | ||
![]() |
0298522fd5 | ||
![]() |
6750e33525 | ||
![]() |
9a67111a0f | ||
![]() |
e6a690558b | ||
![]() |
a3bc559fa3 | ||
![]() |
3a1072a158 | ||
![]() |
0841bf8529 | ||
![]() |
2b82c222b0 | ||
![]() |
d966129fd8 | ||
![]() |
84752b3b13 | ||
![]() |
3735c2e761 | ||
![]() |
9fc89ba744 | ||
![]() |
f7603a421f | ||
![]() |
6631e9e939 | ||
![]() |
3e4e84e3a7 | ||
![]() |
ce63bb0842 | ||
![]() |
d1121478ab | ||
![]() |
9527390736 | ||
![]() |
a732271793 | ||
![]() |
b532267596 | ||
![]() |
b14c07a60c | ||
![]() |
3374169c74 | ||
![]() |
ffb8872f95 | ||
![]() |
d2fb4675e1 | ||
![]() |
569ea0cc01 | ||
![]() |
00f034cef2 | ||
![]() |
615691efc3 | ||
![]() |
760d2f1f0a | ||
![]() |
8bb952e416 | ||
![]() |
f29e0bf53e | ||
![]() |
28aab33cd1 | ||
![]() |
d79f89e168 | ||
![]() |
699cc7213d | ||
![]() |
f65d8e1254 | ||
![]() |
fb297981af | ||
![]() |
8bf1c21738 | ||
![]() |
943861a8a3 | ||
![]() |
450fd7f2b5 | ||
![]() |
2d5da3e958 | ||
![]() |
66a63b983e | ||
![]() |
335362ffc4 | ||
![]() |
17bdb9558a | ||
![]() |
9738bffc3f | ||
![]() |
f58d200ecb | ||
![]() |
215987d5a7 | ||
![]() |
16227704d7 | ||
![]() |
aad375b713 | ||
![]() |
104d372dfa | ||
![]() |
7960206e2e | ||
![]() |
4f5ec3e360 | ||
![]() |
ead457f312 | ||
![]() |
575f57a24e | ||
![]() |
64da8cd47d | ||
![]() |
0e662c4007 | ||
![]() |
6a8a656fef | ||
![]() |
cfc023e128 | ||
![]() |
15b2473224 | ||
![]() |
48eeb55198 | ||
![]() |
4cd024d91e | ||
![]() |
fa4a912a86 | ||
![]() |
209da6f338 | ||
![]() |
ec5e9fcd0d | ||
![]() |
e9eb7edda6 | ||
![]() |
b60b06a062 | ||
![]() |
efe8b46576 | ||
![]() |
91b8eea6ad | ||
![]() |
b6a4a0d9af | ||
![]() |
7b3cc9fe1f | ||
![]() |
2c39038507 | ||
![]() |
5bfe5b3f70 | ||
![]() |
d229787fa6 | ||
![]() |
1836c7a358 | ||
![]() |
1b83ce8759 | ||
![]() |
8c72a57344 | ||
![]() |
3f47bf6b77 | ||
![]() |
40f480c24e | ||
![]() |
8e716780b7 | ||
![]() |
b77b22b01a | ||
![]() |
1194690c42 | ||
![]() |
2657668a86 | ||
![]() |
f5dd25c87f | ||
![]() |
0acc52b23b | ||
![]() |
d4b085081a |
201
.coveragerc
201
.coveragerc
@@ -8,18 +8,36 @@ omit =
|
||||
homeassistant/helpers/signal.py
|
||||
|
||||
# omit pieces of code that rely on external devices being present
|
||||
homeassistant/components/abode.py
|
||||
homeassistant/components/*/abode.py
|
||||
|
||||
homeassistant/components/alarmdecoder.py
|
||||
homeassistant/components/*/alarmdecoder.py
|
||||
|
||||
homeassistant/components/amcrest.py
|
||||
homeassistant/components/*/amcrest.py
|
||||
|
||||
homeassistant/components/apcupsd.py
|
||||
homeassistant/components/*/apcupsd.py
|
||||
|
||||
homeassistant/components/apple_tv.py
|
||||
homeassistant/components/*/apple_tv.py
|
||||
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
homeassistant/components/android_ip_webcam.py
|
||||
homeassistant/components/*/android_ip_webcam.py
|
||||
|
||||
homeassistant/components/arlo.py
|
||||
homeassistant/components/*/arlo.py
|
||||
|
||||
homeassistant/components/asterisk_mbox.py
|
||||
homeassistant/components/*/asterisk_mbox.py
|
||||
|
||||
homeassistant/components/axis.py
|
||||
homeassistant/components/*/axis.py
|
||||
|
||||
homeassistant/components/bbb_gpio.py
|
||||
homeassistant/components/*/bbb_gpio.py
|
||||
|
||||
@@ -29,23 +47,35 @@ omit =
|
||||
homeassistant/components/bloomsky.py
|
||||
homeassistant/components/*/bloomsky.py
|
||||
|
||||
homeassistant/components/comfoconnect.py
|
||||
homeassistant/components/*/comfoconnect.py
|
||||
|
||||
homeassistant/components/digital_ocean.py
|
||||
homeassistant/components/*/digital_ocean.py
|
||||
|
||||
homeassistant/components/dweet.py
|
||||
homeassistant/components/*/dweet.py
|
||||
|
||||
homeassistant/components/eight_sleep.py
|
||||
homeassistant/components/*/eight_sleep.py
|
||||
|
||||
homeassistant/components/ecobee.py
|
||||
homeassistant/components/*/ecobee.py
|
||||
|
||||
homeassistant/components/enocean.py
|
||||
homeassistant/components/*/enocean.py
|
||||
|
||||
homeassistant/components/envisalink.py
|
||||
homeassistant/components/*/envisalink.py
|
||||
|
||||
homeassistant/components/google.py
|
||||
homeassistant/components/*/google.py
|
||||
|
||||
homeassistant/components/insteon_hub.py
|
||||
homeassistant/components/*/insteon_hub.py
|
||||
homeassistant/components/hdmi_cec.py
|
||||
homeassistant/components/*/hdmi_cec.py
|
||||
|
||||
homeassistant/components/homematic.py
|
||||
homeassistant/components/*/homematic.py
|
||||
|
||||
homeassistant/components/insteon_local.py
|
||||
homeassistant/components/*/insteon_local.py
|
||||
@@ -59,36 +89,81 @@ omit =
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/*/isy994.py
|
||||
|
||||
homeassistant/components/joaoapps_join.py
|
||||
homeassistant/components/*/joaoapps_join.py
|
||||
|
||||
homeassistant/components/juicenet.py
|
||||
homeassistant/components/*/juicenet.py
|
||||
|
||||
homeassistant/components/kira.py
|
||||
homeassistant/components/*/kira.py
|
||||
|
||||
homeassistant/components/knx.py
|
||||
homeassistant/components/*/knx.py
|
||||
|
||||
homeassistant/components/lametric.py
|
||||
homeassistant/components/*/lametric.py
|
||||
|
||||
homeassistant/components/lutron.py
|
||||
homeassistant/components/*/lutron.py
|
||||
|
||||
homeassistant/components/lutron_caseta.py
|
||||
homeassistant/components/*/lutron_caseta.py
|
||||
|
||||
homeassistant/components/mailgun.py
|
||||
homeassistant/components/*/mailgun.py
|
||||
|
||||
homeassistant/components/maxcube.py
|
||||
homeassistant/components/*/maxcube.py
|
||||
|
||||
homeassistant/components/mochad.py
|
||||
homeassistant/components/*/mochad.py
|
||||
|
||||
homeassistant/components/modbus.py
|
||||
homeassistant/components/*/modbus.py
|
||||
|
||||
homeassistant/components/mysensors.py
|
||||
homeassistant/components/*/mysensors.py
|
||||
|
||||
homeassistant/components/neato.py
|
||||
homeassistant/components/*/neato.py
|
||||
|
||||
homeassistant/components/nest.py
|
||||
homeassistant/components/*/nest.py
|
||||
|
||||
homeassistant/components/netatmo.py
|
||||
homeassistant/components/*/netatmo.py
|
||||
|
||||
homeassistant/components/octoprint.py
|
||||
homeassistant/components/*/octoprint.py
|
||||
|
||||
homeassistant/components/opencv.py
|
||||
homeassistant/components/*/opencv.py
|
||||
|
||||
homeassistant/components/qwikswitch.py
|
||||
homeassistant/components/*/qwikswitch.py
|
||||
|
||||
homeassistant/components/rachio.py
|
||||
homeassistant/components/*/rachio.py
|
||||
|
||||
homeassistant/components/raspihats.py
|
||||
homeassistant/components/*/raspihats.py
|
||||
|
||||
homeassistant/components/rfxtrx.py
|
||||
homeassistant/components/*/rfxtrx.py
|
||||
|
||||
homeassistant/components/rpi_gpio.py
|
||||
homeassistant/components/*/rpi_gpio.py
|
||||
|
||||
homeassistant/components/rpi_pfio.py
|
||||
homeassistant/components/*/rpi_pfio.py
|
||||
|
||||
homeassistant/components/scsgate.py
|
||||
homeassistant/components/*/scsgate.py
|
||||
|
||||
homeassistant/components/tado.py
|
||||
homeassistant/components/*/tado.py
|
||||
|
||||
homeassistant/components/tellduslive.py
|
||||
homeassistant/components/*/tellduslive.py
|
||||
|
||||
@@ -104,6 +179,15 @@ omit =
|
||||
homeassistant/components/notify/twilio_sms.py
|
||||
homeassistant/components/notify/twilio_call.py
|
||||
|
||||
homeassistant/components/usps.py
|
||||
homeassistant/components/*/usps.py
|
||||
|
||||
homeassistant/components/velbus.py
|
||||
homeassistant/components/*/velbus.py
|
||||
|
||||
homeassistant/components/velux.py
|
||||
homeassistant/components/*/velux.py
|
||||
|
||||
homeassistant/components/vera.py
|
||||
homeassistant/components/*/vera.py
|
||||
|
||||
@@ -121,41 +205,30 @@ omit =
|
||||
homeassistant/components/wink.py
|
||||
homeassistant/components/*/wink.py
|
||||
|
||||
homeassistant/components/zigbee.py
|
||||
homeassistant/components/*/zigbee.py
|
||||
|
||||
homeassistant/components/enocean.py
|
||||
homeassistant/components/*/enocean.py
|
||||
|
||||
homeassistant/components/netatmo.py
|
||||
homeassistant/components/*/netatmo.py
|
||||
|
||||
homeassistant/components/neato.py
|
||||
homeassistant/components/*/neato.py
|
||||
|
||||
homeassistant/components/homematic.py
|
||||
homeassistant/components/*/homematic.py
|
||||
|
||||
homeassistant/components/knx.py
|
||||
homeassistant/components/*/knx.py
|
||||
|
||||
homeassistant/components/zoneminder.py
|
||||
homeassistant/components/*/zoneminder.py
|
||||
|
||||
homeassistant/components/mochad.py
|
||||
homeassistant/components/*/mochad.py
|
||||
homeassistant/components/xiaomi.py
|
||||
homeassistant/components/binary_sensor/xiaomi.py
|
||||
homeassistant/components/cover/xiaomi.py
|
||||
homeassistant/components/light/xiaomi.py
|
||||
homeassistant/components/sensor/xiaomi.py
|
||||
homeassistant/components/switch/xiaomi.py
|
||||
|
||||
homeassistant/components/zabbix.py
|
||||
homeassistant/components/*/zabbix.py
|
||||
|
||||
homeassistant/components/maxcube.py
|
||||
homeassistant/components/*/maxcube.py
|
||||
homeassistant/components/zha/__init__.py
|
||||
homeassistant/components/zha/const.py
|
||||
homeassistant/components/*/zha.py
|
||||
|
||||
homeassistant/components/tado.py
|
||||
homeassistant/components/*/tado.py
|
||||
homeassistant/components/zigbee.py
|
||||
homeassistant/components/*/zigbee.py
|
||||
|
||||
homeassistant/components/zoneminder.py
|
||||
homeassistant/components/*/zoneminder.py
|
||||
|
||||
homeassistant/components/alarm_control_panel/alarmdotcom.py
|
||||
homeassistant/components/alarm_control_panel/concord232.py
|
||||
homeassistant/components/alarm_control_panel/egardia.py
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py
|
||||
homeassistant/components/alarm_control_panel/nx584.py
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py
|
||||
homeassistant/components/alarm_control_panel/totalconnect.py
|
||||
@@ -165,32 +238,39 @@ omit =
|
||||
homeassistant/components/binary_sensor/flic.py
|
||||
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
|
||||
homeassistant/components/browser.py
|
||||
homeassistant/components/camera/amcrest.py
|
||||
homeassistant/components/camera/bloomsky.py
|
||||
homeassistant/components/camera/ffmpeg.py
|
||||
homeassistant/components/camera/foscam.py
|
||||
homeassistant/components/camera/mjpeg.py
|
||||
homeassistant/components/camera/rpi_camera.py
|
||||
homeassistant/components/camera/onvif.py
|
||||
homeassistant/components/camera/synology.py
|
||||
homeassistant/components/climate/eq3btsmart.py
|
||||
homeassistant/components/climate/flexit.py
|
||||
homeassistant/components/climate/heatmiser.py
|
||||
homeassistant/components/climate/homematic.py
|
||||
homeassistant/components/climate/knx.py
|
||||
homeassistant/components/climate/oem.py
|
||||
homeassistant/components/climate/proliphix.py
|
||||
homeassistant/components/climate/radiotherm.py
|
||||
homeassistant/components/climate/sensibo.py
|
||||
homeassistant/components/cover/garadget.py
|
||||
homeassistant/components/cover/homematic.py
|
||||
homeassistant/components/cover/knx.py
|
||||
homeassistant/components/cover/myq.py
|
||||
homeassistant/components/cover/opengarage.py
|
||||
homeassistant/components/cover/rpi_gpio.py
|
||||
homeassistant/components/cover/scsgate.py
|
||||
homeassistant/components/cover/wink.py
|
||||
homeassistant/components/device_tracker/actiontec.py
|
||||
homeassistant/components/device_tracker/aruba.py
|
||||
homeassistant/components/device_tracker/asuswrt.py
|
||||
homeassistant/components/device_tracker/automatic.py
|
||||
homeassistant/components/device_tracker/bbox.py
|
||||
homeassistant/components/device_tracker/bluetooth_le_tracker.py
|
||||
homeassistant/components/device_tracker/bluetooth_tracker.py
|
||||
@@ -198,9 +278,12 @@ omit =
|
||||
homeassistant/components/device_tracker/cisco_ios.py
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/gpslogger.py
|
||||
homeassistant/components/device_tracker/huawei_router.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
homeassistant/components/device_tracker/linksys_ap.py
|
||||
homeassistant/components/device_tracker/linksys_smart.py
|
||||
homeassistant/components/device_tracker/luci.py
|
||||
homeassistant/components/device_tracker/mikrotik.py
|
||||
homeassistant/components/device_tracker/netgear.py
|
||||
homeassistant/components/device_tracker/nmap_tracker.py
|
||||
homeassistant/components/device_tracker/ping.py
|
||||
@@ -213,43 +296,50 @@ omit =
|
||||
homeassistant/components/device_tracker/tplink.py
|
||||
homeassistant/components/device_tracker/trackr.py
|
||||
homeassistant/components/device_tracker/ubus.py
|
||||
homeassistant/components/device_tracker/xiaomi.py
|
||||
homeassistant/components/downloader.py
|
||||
homeassistant/components/emoncms_history.py
|
||||
homeassistant/components/emulated_hue/upnp.py
|
||||
homeassistant/components/fan/mqtt.py
|
||||
homeassistant/components/feedreader.py
|
||||
homeassistant/components/foursquare.py
|
||||
homeassistant/components/hdmi_cec.py
|
||||
homeassistant/components/ifttt.py
|
||||
homeassistant/components/joaoapps_join.py
|
||||
homeassistant/components/image_processing/dlib_face_detect.py
|
||||
homeassistant/components/image_processing/dlib_face_identify.py
|
||||
homeassistant/components/image_processing/seven_segments.py
|
||||
homeassistant/components/keyboard.py
|
||||
homeassistant/components/keyboard_remote.py
|
||||
homeassistant/components/light/avion.py
|
||||
homeassistant/components/light/blinkt.py
|
||||
homeassistant/components/light/blinksticklight.py
|
||||
homeassistant/components/light/decora.py
|
||||
homeassistant/components/light/decora_wifi.py
|
||||
homeassistant/components/light/flux_led.py
|
||||
homeassistant/components/light/hue.py
|
||||
homeassistant/components/light/hyperion.py
|
||||
homeassistant/components/light/lifx/*.py
|
||||
homeassistant/components/light/lifx.py
|
||||
homeassistant/components/light/lifx_legacy.py
|
||||
homeassistant/components/light/limitlessled.py
|
||||
homeassistant/components/light/mystrom.py
|
||||
homeassistant/components/light/osramlightify.py
|
||||
homeassistant/components/light/rpi_gpio_pwm.py
|
||||
homeassistant/components/light/piglow.py
|
||||
homeassistant/components/light/sensehat.py
|
||||
homeassistant/components/light/tikteck.py
|
||||
homeassistant/components/light/tplink.py
|
||||
homeassistant/components/light/tradfri.py
|
||||
homeassistant/components/light/x10.py
|
||||
homeassistant/components/light/yeelight.py
|
||||
homeassistant/components/light/yeelightsunflower.py
|
||||
homeassistant/components/light/zengge.py
|
||||
homeassistant/components/lirc.py
|
||||
homeassistant/components/lock/nello.py
|
||||
homeassistant/components/lock/nuki.py
|
||||
homeassistant/components/lock/lockitron.py
|
||||
homeassistant/components/lock/sesame.py
|
||||
homeassistant/components/media_extractor.py
|
||||
homeassistant/components/media_player/anthemav.py
|
||||
homeassistant/components/media_player/apple_tv.py
|
||||
homeassistant/components/media_player/aquostv.py
|
||||
homeassistant/components/media_player/bluesound.py
|
||||
homeassistant/components/media_player/braviatv.py
|
||||
homeassistant/components/media_player/cast.py
|
||||
homeassistant/components/media_player/clementine.py
|
||||
@@ -263,7 +353,6 @@ omit =
|
||||
homeassistant/components/media_player/frontier_silicon.py
|
||||
homeassistant/components/media_player/gpmdp.py
|
||||
homeassistant/components/media_player/gstreamer.py
|
||||
homeassistant/components/media_player/hdmi_cec.py
|
||||
homeassistant/components/media_player/itunes.py
|
||||
homeassistant/components/media_player/kodi.py
|
||||
homeassistant/components/media_player/lg_netcast.py
|
||||
@@ -271,6 +360,7 @@ omit =
|
||||
homeassistant/components/media_player/mpchc.py
|
||||
homeassistant/components/media_player/mpd.py
|
||||
homeassistant/components/media_player/nad.py
|
||||
homeassistant/components/media_player/nadtcp.py
|
||||
homeassistant/components/media_player/onkyo.py
|
||||
homeassistant/components/media_player/openhome.py
|
||||
homeassistant/components/media_player/panasonic_viera.py
|
||||
@@ -279,12 +369,14 @@ omit =
|
||||
homeassistant/components/media_player/pioneer.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/roku.py
|
||||
homeassistant/components/media_player/russound_rio.py
|
||||
homeassistant/components/media_player/russound_rnet.py
|
||||
homeassistant/components/media_player/samsungtv.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
homeassistant/components/media_player/sonos.py
|
||||
homeassistant/components/media_player/spotify.py
|
||||
homeassistant/components/media_player/squeezebox.py
|
||||
homeassistant/components/media_player/vizio.py
|
||||
homeassistant/components/media_player/vlc.py
|
||||
homeassistant/components/media_player/volumio.py
|
||||
homeassistant/components/media_player/yamaha.py
|
||||
@@ -292,21 +384,22 @@ omit =
|
||||
homeassistant/components/notify/aws_sns.py
|
||||
homeassistant/components/notify/aws_sqs.py
|
||||
homeassistant/components/notify/ciscospark.py
|
||||
homeassistant/components/notify/clicksend.py
|
||||
homeassistant/components/notify/discord.py
|
||||
homeassistant/components/notify/facebook.py
|
||||
homeassistant/components/notify/free_mobile.py
|
||||
homeassistant/components/notify/gntp.py
|
||||
homeassistant/components/notify/group.py
|
||||
homeassistant/components/notify/hipchat.py
|
||||
homeassistant/components/notify/instapush.py
|
||||
homeassistant/components/notify/joaoapps_join.py
|
||||
homeassistant/components/notify/kodi.py
|
||||
homeassistant/components/notify/lannouncer.py
|
||||
homeassistant/components/notify/llamalab_automate.py
|
||||
homeassistant/components/notify/mailgun.py
|
||||
homeassistant/components/notify/matrix.py
|
||||
homeassistant/components/notify/message_bird.py
|
||||
homeassistant/components/notify/nfandroidtv.py
|
||||
homeassistant/components/notify/nma.py
|
||||
homeassistant/components/notify/prowl.py
|
||||
homeassistant/components/notify/pushbullet.py
|
||||
homeassistant/components/notify/pushetta.py
|
||||
homeassistant/components/notify/pushover.py
|
||||
@@ -322,19 +415,24 @@ omit =
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/nuimo_controller.py
|
||||
homeassistant/components/prometheus.py
|
||||
homeassistant/components/remote/harmony.py
|
||||
homeassistant/components/remote/itach.py
|
||||
homeassistant/components/scene/hunterdouglas_powerview.py
|
||||
homeassistant/components/scene/lifx_cloud.py
|
||||
homeassistant/components/sensor/amcrest.py
|
||||
homeassistant/components/sensor/arest.py
|
||||
homeassistant/components/sensor/arwn.py
|
||||
homeassistant/components/sensor/bbox.py
|
||||
homeassistant/components/sensor/bh1750.py
|
||||
homeassistant/components/sensor/bitcoin.py
|
||||
homeassistant/components/sensor/blockchain.py
|
||||
homeassistant/components/sensor/bme280.py
|
||||
homeassistant/components/sensor/bom.py
|
||||
homeassistant/components/sensor/broadlink.py
|
||||
homeassistant/components/sensor/dublin_bus_transport.py
|
||||
homeassistant/components/sensor/buienradar.py
|
||||
homeassistant/components/sensor/citybikes.py
|
||||
homeassistant/components/sensor/coinmarketcap.py
|
||||
homeassistant/components/sensor/cert_expiry.py
|
||||
homeassistant/components/sensor/comed_hourly_pricing.py
|
||||
homeassistant/components/sensor/cpuspeed.py
|
||||
homeassistant/components/sensor/crimereports.py
|
||||
@@ -346,10 +444,13 @@ omit =
|
||||
homeassistant/components/sensor/dnsip.py
|
||||
homeassistant/components/sensor/dovado.py
|
||||
homeassistant/components/sensor/dte_energy_bridge.py
|
||||
homeassistant/components/sensor/dublin_bus_transport.py
|
||||
homeassistant/components/sensor/ebox.py
|
||||
homeassistant/components/sensor/eddystone_temperature.py
|
||||
homeassistant/components/sensor/eliqonline.py
|
||||
homeassistant/components/sensor/emoncms.py
|
||||
homeassistant/components/sensor/envirophat.py
|
||||
homeassistant/components/sensor/etherscan.py
|
||||
homeassistant/components/sensor/fastdotcom.py
|
||||
homeassistant/components/sensor/fedex.py
|
||||
homeassistant/components/sensor/fido.py
|
||||
@@ -357,6 +458,8 @@ omit =
|
||||
homeassistant/components/sensor/fixer.py
|
||||
homeassistant/components/sensor/fritzbox_callmonitor.py
|
||||
homeassistant/components/sensor/fritzbox_netmonitor.py
|
||||
homeassistant/components/sensor/geizhals.py
|
||||
homeassistant/components/sensor/gitter.py
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/google_travel_time.py
|
||||
homeassistant/components/sensor/gpsd.py
|
||||
@@ -364,6 +467,7 @@ omit =
|
||||
homeassistant/components/sensor/haveibeenpwned.py
|
||||
homeassistant/components/sensor/hddtemp.py
|
||||
homeassistant/components/sensor/hp_ilo.py
|
||||
homeassistant/components/sensor/htu21d.py
|
||||
homeassistant/components/sensor/hydroquebec.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/imap_email_content.py
|
||||
@@ -388,15 +492,20 @@ omit =
|
||||
homeassistant/components/sensor/openexchangerates.py
|
||||
homeassistant/components/sensor/opensky.py
|
||||
homeassistant/components/sensor/openweathermap.py
|
||||
homeassistant/components/sensor/otp.py
|
||||
homeassistant/components/sensor/pi_hole.py
|
||||
homeassistant/components/sensor/plex.py
|
||||
homeassistant/components/sensor/pocketcasts.py
|
||||
homeassistant/components/sensor/pushbullet.py
|
||||
homeassistant/components/sensor/pvoutput.py
|
||||
homeassistant/components/sensor/qnap.py
|
||||
homeassistant/components/sensor/radarr.py
|
||||
homeassistant/components/sensor/ripple.py
|
||||
homeassistant/components/sensor/sabnzbd.py
|
||||
homeassistant/components/sensor/scrape.py
|
||||
homeassistant/components/sensor/sensehat.py
|
||||
homeassistant/components/sensor/serial_pm.py
|
||||
homeassistant/components/sensor/shodan.py
|
||||
homeassistant/components/sensor/skybeacon.py
|
||||
homeassistant/components/sensor/sma.py
|
||||
homeassistant/components/sensor/snmp.py
|
||||
@@ -415,13 +524,16 @@ omit =
|
||||
homeassistant/components/sensor/transmission.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/ups.py
|
||||
homeassistant/components/sensor/usps.py
|
||||
homeassistant/components/sensor/vasttrafik.py
|
||||
homeassistant/components/sensor/waqi.py
|
||||
homeassistant/components/sensor/worldtidesinfo.py
|
||||
homeassistant/components/sensor/xbox_live.py
|
||||
homeassistant/components/sensor/yweather.py
|
||||
homeassistant/components/sensor/zamg.py
|
||||
homeassistant/components/shiftr.py
|
||||
homeassistant/components/spc.py
|
||||
homeassistant/components/switch/acer_projector.py
|
||||
homeassistant/components/switch/anel_pwrctrl.py
|
||||
homeassistant/components/switch/arest.py
|
||||
@@ -430,7 +542,6 @@ omit =
|
||||
homeassistant/components/switch/dlink.py
|
||||
homeassistant/components/switch/edimax.py
|
||||
homeassistant/components/switch/fritzdect.py
|
||||
homeassistant/components/switch/hdmi_cec.py
|
||||
homeassistant/components/switch/hikvisioncam.py
|
||||
homeassistant/components/switch/hook.py
|
||||
homeassistant/components/switch/kankun.py
|
||||
@@ -439,6 +550,7 @@ omit =
|
||||
homeassistant/components/switch/orvibo.py
|
||||
homeassistant/components/switch/pilight.py
|
||||
homeassistant/components/switch/pulseaudio_loopback.py
|
||||
homeassistant/components/switch/rainmachine.py
|
||||
homeassistant/components/switch/rest.py
|
||||
homeassistant/components/switch/rpi_rf.py
|
||||
homeassistant/components/switch/tplink.py
|
||||
@@ -449,9 +561,12 @@ omit =
|
||||
homeassistant/components/tts/amazon_polly.py
|
||||
homeassistant/components/tts/picotts.py
|
||||
homeassistant/components/upnp.py
|
||||
homeassistant/components/vacuum/roomba.py
|
||||
homeassistant/components/weather/bom.py
|
||||
homeassistant/components/weather/buienradar.py
|
||||
homeassistant/components/weather/metoffice.py
|
||||
homeassistant/components/weather/openweathermap.py
|
||||
homeassistant/components/weather/yweather.py
|
||||
homeassistant/components/weather/zamg.py
|
||||
homeassistant/components/zeroconf.py
|
||||
homeassistant/components/zwave/util.py
|
||||
|
@@ -1,2 +1,14 @@
|
||||
.tox
|
||||
# General files
|
||||
.git
|
||||
.github
|
||||
config
|
||||
|
||||
# Test related files
|
||||
.tox
|
||||
|
||||
# Other virtualization methods
|
||||
venv
|
||||
.vagrant
|
||||
|
||||
# Temporary files
|
||||
**/__pycache__
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -73,6 +73,7 @@ pyvenv.cfg
|
||||
pip-selfcheck.json
|
||||
venv
|
||||
.venv
|
||||
Pipfile*
|
||||
|
||||
# vimmy stuff
|
||||
*.swp
|
||||
@@ -93,3 +94,6 @@ docs/build
|
||||
|
||||
# Windows Explorer
|
||||
desktop.ini
|
||||
/home-assistant.pyproj
|
||||
/home-assistant.sln
|
||||
/.vs/home-assistant/v14
|
||||
|
18
.travis.yml
18
.travis.yml
@@ -1,21 +1,25 @@
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libudev-dev
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=py34
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=requirements
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=lint
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=py34
|
||||
# - python: "3.5"
|
||||
# env: TOXENV=typing
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36
|
||||
- python: "3.6-dev"
|
||||
env: TOXENV=py36
|
||||
# - python: "3.6-dev"
|
||||
# env: TOXENV=py36
|
||||
- python: "3.4.2"
|
||||
env: TOXENV=requirements
|
||||
# allow_failures:
|
||||
# - python: "3.5"
|
||||
# env: TOXENV=typing
|
||||
@@ -25,5 +29,5 @@ cache:
|
||||
- $HOME/.cache/pip
|
||||
install: pip install -U tox coveralls
|
||||
language: python
|
||||
script: tox
|
||||
script: travis_wait tox
|
||||
after_success: coveralls
|
||||
|
41
CODEOWNERS
Normal file
41
CODEOWNERS
Normal file
@@ -0,0 +1,41 @@
|
||||
# People marked here will be automatically requested for a review
|
||||
# when the code that they own is touched.
|
||||
# https://github.com/blog/2392-introducing-code-owners
|
||||
|
||||
setup.py @home-assistant/core
|
||||
homeassistant/*.py @home-assistant/core
|
||||
homeassistant/helpers/* @home-assistant/core
|
||||
homeassistant/util/* @home-assistant/core
|
||||
homeassistant/components/api.py @home-assistant/core
|
||||
homeassistant/components/automation/* @home-assistant/core
|
||||
homeassistant/components/configurator.py @home-assistant/core
|
||||
homeassistant/components/group.py @home-assistant/core
|
||||
homeassistant/components/history.py @home-assistant/core
|
||||
homeassistant/components/http/* @home-assistant/core
|
||||
homeassistant/components/input_*.py @home-assistant/core
|
||||
homeassistant/components/introduction.py @home-assistant/core
|
||||
homeassistant/components/logger.py @home-assistant/core
|
||||
homeassistant/components/mqtt/* @home-assistant/core
|
||||
homeassistant/components/panel_custom.py @home-assistant/core
|
||||
homeassistant/components/panel_iframe.py @home-assistant/core
|
||||
homeassistant/components/persistent_notification.py @home-assistant/core
|
||||
homeassistant/components/scene/__init__.py @home-assistant/core
|
||||
homeassistant/components/scene/hass.py @home-assistant/core
|
||||
homeassistant/components/script.py @home-assistant/core
|
||||
homeassistant/components/shell_command.py @home-assistant/core
|
||||
homeassistant/components/sun.py @home-assistant/core
|
||||
homeassistant/components/updater.py @home-assistant/core
|
||||
homeassistant/components/weblink.py @home-assistant/core
|
||||
homeassistant/components/websocket_api.py @home-assistant/core
|
||||
homeassistant/components/zone.py @home-assistant/core
|
||||
|
||||
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/cover/template.py @PhracturedBlue
|
||||
homeassistant/components/device_tracker/automatic.py @armills
|
||||
homeassistant/components/media_player/kodi.py @armills
|
@@ -4,11 +4,11 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
|
||||
|
||||
The process is straight-forward.
|
||||
|
||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) by Kubernetes (but skip step 0)
|
||||
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
|
||||
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
|
||||
- Write the code for your device, notification service, sensor, or IoT thing.
|
||||
- Ensure tests work.
|
||||
- Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant.
|
||||
|
||||
Still interested? Then you should take a peak at the [developer documentation](https://home-assistant.io/developers/) to get more details.
|
||||
Still interested? Then you should take a peek at the [developer documentation](https://home-assistant.io/developers/) to get more details.
|
||||
|
||||
|
13
Dockerfile
13
Dockerfile
@@ -1,14 +1,18 @@
|
||||
FROM python:3.5
|
||||
# Notice:
|
||||
# When updating this file, please also update virtualization/Docker/Dockerfile.dev
|
||||
# This way, the development image and the production image are kept in sync.
|
||||
|
||||
FROM python:3.6
|
||||
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
|
||||
|
||||
# Uncomment any of the following lines to disable the installation.
|
||||
#ENV INSTALL_TELLSTICK no
|
||||
#ENV INSTALL_OPENALPR no
|
||||
#ENV INSTALL_FFMPEG no
|
||||
#ENV INSTALL_OPENZWAVE no
|
||||
#ENV INSTALL_LIBCEC no
|
||||
#ENV INSTALL_PHANTOMJS no
|
||||
#ENV INSTALL_COAP_CLIENT no
|
||||
#ENV INSTALL_SSOCR no
|
||||
|
||||
VOLUME /config
|
||||
|
||||
@@ -21,10 +25,13 @@ RUN virtualization/Docker/setup_docker_prereqs
|
||||
|
||||
# Install hass component dependencies
|
||||
COPY requirements_all.txt requirements_all.txt
|
||||
|
||||
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
|
||||
# See PR #8103 for more info.
|
||||
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
|
||||
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
|
||||
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
||||
CMD [ "python", "-m", "homeassistant", "--config", "/config" ]
|
||||
|
@@ -1,5 +1,5 @@
|
||||
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs|
|
||||
==============================================================================================================================================================================================
|
||||
Home Assistant |Build Status| |Coverage Status| |Chat Status|
|
||||
=============================================================
|
||||
|
||||
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
|
||||
|
||||
@@ -31,6 +31,8 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
|
||||
:target: https://travis-ci.org/home-assistant/home-assistant
|
||||
.. |Coverage Status| image:: https://img.shields.io/coveralls/home-assistant/home-assistant.svg
|
||||
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
|
||||
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
|
||||
:target: https://discord.gg/c5DvZ4e
|
||||
.. |Join the chat at https://gitter.im/home-assistant/home-assistant| image:: https://img.shields.io/badge/gitter-general-blue.svg
|
||||
:target: https://gitter.im/home-assistant/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
.. |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs| image:: https://img.shields.io/badge/gitter-development-yellowgreen.svg
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<ul>
|
||||
<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://home-assistant.io/">🏡 Homepage</a></li>
|
||||
<li><a href="https://gitter.im/home-assistant/home-assistant">💬 Gitter</a></li>
|
||||
<li><a href="https://pypi.python.org/pypi/homeassistant">💾 Download Releases</a></li>
|
||||
<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>
|
||||
</ul>
|
||||
<hr>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
"""Starts home assistant."""
|
||||
"""Start Home Assistant."""
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
@@ -10,6 +10,7 @@ import threading
|
||||
|
||||
from typing import Optional, List
|
||||
|
||||
from homeassistant import monkey_patch
|
||||
from homeassistant.const import (
|
||||
__version__,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
@@ -17,7 +18,6 @@ from homeassistant.const import (
|
||||
REQUIRED_PYTHON_VER_WIN,
|
||||
RESTART_EXIT_CODE,
|
||||
)
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
|
||||
def attempt_use_uvloop():
|
||||
@@ -31,50 +31,8 @@ def attempt_use_uvloop():
|
||||
pass
|
||||
|
||||
|
||||
def monkey_patch_asyncio():
|
||||
"""Replace weakref.WeakSet to address Python 3 bug.
|
||||
|
||||
Under heavy threading operations that schedule calls into
|
||||
the asyncio event loop, Task objects are created. Due to
|
||||
a bug in Python, GC may have an issue when switching between
|
||||
the threads and objects with __del__ (which various components
|
||||
in HASS have).
|
||||
|
||||
This monkey-patch removes the weakref.Weakset, and replaces it
|
||||
with an object that ignores the only call utilizing it (the
|
||||
Task.__init__ which calls _all_tasks.add(self)). It also removes
|
||||
the __del__ which could trigger the future objects __del__ at
|
||||
unpredictable times.
|
||||
|
||||
The side-effect of this manipulation of the Task is that
|
||||
Task.all_tasks() is no longer accurate, and there will be no
|
||||
warning emitted if a Task is GC'd while in use.
|
||||
|
||||
On Python 3.6, after the bug is fixed, this monkey-patch can be
|
||||
disabled.
|
||||
|
||||
See https://bugs.python.org/issue26617 for details of the Python
|
||||
bug.
|
||||
"""
|
||||
# pylint: disable=no-self-use, protected-access, bare-except
|
||||
import asyncio.tasks
|
||||
|
||||
class IgnoreCalls:
|
||||
"""Ignore add calls."""
|
||||
|
||||
def add(self, other):
|
||||
"""No-op add."""
|
||||
return
|
||||
|
||||
asyncio.tasks.Task._all_tasks = IgnoreCalls()
|
||||
try:
|
||||
del asyncio.tasks.Task.__del__
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def validate_python() -> None:
|
||||
"""Validate we're running the right Python version."""
|
||||
"""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(
|
||||
@@ -215,7 +173,7 @@ def daemonize() -> None:
|
||||
|
||||
|
||||
def check_pid(pid_file: str) -> None:
|
||||
"""Check that HA is not already running."""
|
||||
"""Check that Home Assistant is not already running."""
|
||||
# Check pid file
|
||||
try:
|
||||
pid = int(open(pid_file, 'r').readline())
|
||||
@@ -271,13 +229,13 @@ def cmdline() -> List[str]:
|
||||
os.environ['PYTHONPATH'] = os.path.dirname(modulepath)
|
||||
return [sys.executable] + [arg for arg in sys.argv if
|
||||
arg != '--daemon']
|
||||
else:
|
||||
return [arg for arg in sys.argv if arg != '--daemon']
|
||||
|
||||
return [arg for arg in sys.argv if arg != '--daemon']
|
||||
|
||||
|
||||
def setup_and_run_hass(config_dir: str,
|
||||
args: argparse.Namespace) -> Optional[int]:
|
||||
"""Setup HASS and run."""
|
||||
"""Set up HASS and run."""
|
||||
from homeassistant import bootstrap
|
||||
|
||||
# Run a simple daemon runner process on Windows to handle restarts
|
||||
@@ -310,6 +268,9 @@ def setup_and_run_hass(config_dir: str,
|
||||
return None
|
||||
|
||||
if args.open_ui:
|
||||
# Imported here to avoid importing asyncio before monkey patch
|
||||
from homeassistant.util.async import run_callback_threadsafe
|
||||
|
||||
def open_browser(event):
|
||||
"""Open the webinterface in a browser."""
|
||||
if hass.config.api is not None:
|
||||
@@ -326,7 +287,7 @@ def setup_and_run_hass(config_dir: str,
|
||||
|
||||
|
||||
def try_to_restart() -> None:
|
||||
"""Attempt to clean up state and start a new homeassistant instance."""
|
||||
"""Attempt to clean up state and start a new Home Assistant instance."""
|
||||
# Things should be mostly shut down already at this point, now just try
|
||||
# to clean up things that may have been left behind.
|
||||
sys.stderr.write('Home Assistant attempting to restart.\n')
|
||||
@@ -358,11 +319,11 @@ def try_to_restart() -> None:
|
||||
else:
|
||||
os.closerange(3, max_fd)
|
||||
|
||||
# Now launch into a new instance of Home-Assistant. If this fails we
|
||||
# Now launch into a new instance of Home Assistant. If this fails we
|
||||
# fall through and exit with error 100 (RESTART_EXIT_CODE) in which case
|
||||
# systemd will restart us when RestartForceExitStatus=100 is set in the
|
||||
# systemd.service file.
|
||||
sys.stderr.write("Restarting Home-Assistant\n")
|
||||
sys.stderr.write("Restarting Home Assistant\n")
|
||||
args = cmdline()
|
||||
os.execv(args[0], args)
|
||||
|
||||
@@ -371,10 +332,12 @@ def main() -> int:
|
||||
"""Start Home Assistant."""
|
||||
validate_python()
|
||||
|
||||
attempt_use_uvloop()
|
||||
if os.environ.get('HASS_NO_MONKEY') != '1':
|
||||
if sys.version_info[:2] >= (3, 6):
|
||||
monkey_patch.disable_c_asyncio()
|
||||
monkey_patch.patch_weakref_tasks()
|
||||
|
||||
if sys.version_info[:3] < (3, 5, 3):
|
||||
monkey_patch_asyncio()
|
||||
attempt_use_uvloop()
|
||||
|
||||
args = get_arguments()
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
"""Provides methods to bootstrap a home assistant instance."""
|
||||
"""Provide methods to bootstrap a Home Assistant instance."""
|
||||
import asyncio
|
||||
import logging
|
||||
import logging.handlers
|
||||
@@ -19,6 +19,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
|
||||
from homeassistant.setup import async_setup_component
|
||||
import homeassistant.loader as loader
|
||||
from homeassistant.util.logging import AsyncHandler
|
||||
from homeassistant.util.package import async_get_user_site, get_user_site
|
||||
from homeassistant.util.yaml import clear_secret_cache
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.signal import async_register_signal_handling
|
||||
@@ -39,7 +40,7 @@ def from_config_dict(config: Dict[str, Any],
|
||||
skip_pip: bool=False,
|
||||
log_rotate_days: Any=None) \
|
||||
-> Optional[core.HomeAssistant]:
|
||||
"""Try to configure Home Assistant from a config dict.
|
||||
"""Try to configure Home Assistant from a configuration dictionary.
|
||||
|
||||
Dynamically loads required components and its dependencies.
|
||||
"""
|
||||
@@ -48,7 +49,8 @@ def from_config_dict(config: Dict[str, Any],
|
||||
if config_dir is not None:
|
||||
config_dir = os.path.abspath(config_dir)
|
||||
hass.config.config_dir = config_dir
|
||||
mount_local_lib_path(config_dir)
|
||||
hass.loop.run_until_complete(
|
||||
async_mount_local_lib_path(config_dir, hass.loop))
|
||||
|
||||
# run task
|
||||
hass = hass.loop.run_until_complete(
|
||||
@@ -69,7 +71,7 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
skip_pip: bool=False,
|
||||
log_rotate_days: Any=None) \
|
||||
-> Optional[core.HomeAssistant]:
|
||||
"""Try to configure Home Assistant from a config dict.
|
||||
"""Try to configure Home Assistant from a configuration dictionary.
|
||||
|
||||
Dynamically loads required components and its dependencies.
|
||||
This method is a coroutine.
|
||||
@@ -83,19 +85,18 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
|
||||
return None
|
||||
|
||||
yield from hass.loop.run_in_executor(
|
||||
None, conf_util.process_ha_config_upgrade, hass)
|
||||
yield from hass.async_add_job(conf_util.process_ha_config_upgrade, hass)
|
||||
|
||||
if enable_log:
|
||||
async_enable_logging(hass, verbose, log_rotate_days)
|
||||
|
||||
hass.config.skip_pip = skip_pip
|
||||
if skip_pip:
|
||||
_LOGGER.warning('Skipping pip installation of required modules. '
|
||||
'This may cause issues.')
|
||||
_LOGGER.warning("Skipping pip installation of required modules. "
|
||||
"This may cause issues")
|
||||
|
||||
if not loader.PREPARED:
|
||||
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
|
||||
yield from hass.async_add_job(loader.prepare, hass)
|
||||
|
||||
# Merge packages
|
||||
conf_util.merge_packages_config(
|
||||
@@ -117,13 +118,13 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
# pylint: disable=not-an-iterable
|
||||
res = yield from core_components.async_setup(hass, config)
|
||||
if not res:
|
||||
_LOGGER.error('Home Assistant core failed to initialize. '
|
||||
'Further initialization aborted.')
|
||||
_LOGGER.error("Home Assistant core failed to initialize. "
|
||||
"further initialization aborted")
|
||||
return hass
|
||||
|
||||
yield from persistent_notification.async_setup(hass, config)
|
||||
|
||||
_LOGGER.info('Home Assistant core initialized')
|
||||
_LOGGER.info("Home Assistant core initialized")
|
||||
|
||||
# stage 1
|
||||
for component in components:
|
||||
@@ -142,7 +143,7 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
stop = time()
|
||||
_LOGGER.info('Home Assistant initialized in %.2fs', stop-start)
|
||||
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
|
||||
|
||||
async_register_signal_handling(hass)
|
||||
return hass
|
||||
@@ -184,16 +185,15 @@ def async_from_config_file(config_path: str,
|
||||
# Set config dir to directory holding config file
|
||||
config_dir = os.path.abspath(os.path.dirname(config_path))
|
||||
hass.config.config_dir = config_dir
|
||||
yield from hass.loop.run_in_executor(
|
||||
None, mount_local_lib_path, config_dir)
|
||||
yield from async_mount_local_lib_path(config_dir, hass.loop)
|
||||
|
||||
async_enable_logging(hass, verbose, log_rotate_days)
|
||||
|
||||
try:
|
||||
config_dict = yield from hass.loop.run_in_executor(
|
||||
None, conf_util.load_yaml_config_file, config_path)
|
||||
config_dict = yield from hass.async_add_job(
|
||||
conf_util.load_yaml_config_file, config_path)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error('Error loading %s: %s', config_path, err)
|
||||
_LOGGER.error("Error loading %s: %s", config_path, err)
|
||||
return None
|
||||
finally:
|
||||
clear_secret_cache()
|
||||
@@ -206,7 +206,7 @@ def async_from_config_file(config_path: str,
|
||||
@core.callback
|
||||
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
|
||||
log_rotate_days=None) -> None:
|
||||
"""Setup the logging.
|
||||
"""Set up the logging.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
@@ -214,12 +214,12 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
|
||||
fmt = ("%(asctime)s %(levelname)s (%(threadName)s) "
|
||||
"[%(name)s] %(message)s")
|
||||
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
|
||||
datefmt = '%y-%m-%d %H:%M:%S'
|
||||
datefmt = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# suppress overly verbose logs from libraries that aren't helpful
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
||||
# Suppress overly verbose logs from libraries that aren't helpful
|
||||
logging.getLogger('requests').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
logging.getLogger('aiohttp.access').setLevel(logging.WARNING)
|
||||
|
||||
try:
|
||||
from colorlog import ColoredFormatter
|
||||
@@ -274,15 +274,27 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
|
||||
|
||||
else:
|
||||
_LOGGER.error(
|
||||
'Unable to setup error log %s (access denied)', err_log_path)
|
||||
"Unable to setup error log %s (access denied)", err_log_path)
|
||||
|
||||
|
||||
def mount_local_lib_path(config_dir: str) -> str:
|
||||
"""Add local library to Python Path."""
|
||||
deps_dir = os.path.join(config_dir, 'deps')
|
||||
lib_dir = get_user_site(deps_dir)
|
||||
if lib_dir not in sys.path:
|
||||
sys.path.insert(0, lib_dir)
|
||||
return deps_dir
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_mount_local_lib_path(config_dir: str,
|
||||
loop: asyncio.AbstractEventLoop) -> str:
|
||||
"""Add local library to Python Path.
|
||||
|
||||
Async friendly.
|
||||
This function is a coroutine.
|
||||
"""
|
||||
deps_dir = os.path.join(config_dir, 'deps')
|
||||
if deps_dir not in sys.path:
|
||||
sys.path.insert(0, os.path.join(config_dir, 'deps'))
|
||||
lib_dir = yield from async_get_user_site(deps_dir, loop=loop)
|
||||
if lib_dir not in sys.path:
|
||||
sys.path.insert(0, lib_dir)
|
||||
return deps_dir
|
||||
|
@@ -15,7 +15,6 @@ 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.loader import get_component
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
|
||||
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
|
||||
@@ -33,25 +32,27 @@ def is_on(hass, entity_id=None):
|
||||
If there is no entity id given we will check all.
|
||||
"""
|
||||
if entity_id:
|
||||
group = get_component('group')
|
||||
|
||||
entity_ids = group.expand_entity_ids(hass, [entity_id])
|
||||
entity_ids = hass.components.group.expand_entity_ids([entity_id])
|
||||
else:
|
||||
entity_ids = hass.states.entity_ids()
|
||||
|
||||
for entity_id in entity_ids:
|
||||
domain = ha.split_entity_id(entity_id)[0]
|
||||
|
||||
module = get_component(domain)
|
||||
for ent_id in entity_ids:
|
||||
domain = ha.split_entity_id(ent_id)[0]
|
||||
|
||||
try:
|
||||
if module.is_on(hass, entity_id):
|
||||
return True
|
||||
component = getattr(hass.components, domain)
|
||||
|
||||
except AttributeError:
|
||||
# module is None or method is_on does not exist
|
||||
_LOGGER.exception("Failed to call %s.is_on for %s",
|
||||
module, entity_id)
|
||||
except ImportError:
|
||||
_LOGGER.error('Failed to call %s.is_on: component not found',
|
||||
domain)
|
||||
continue
|
||||
|
||||
if not hasattr(component, 'is_on'):
|
||||
_LOGGER.warning("Component %s has no is_on method.", domain)
|
||||
continue
|
||||
|
||||
if component.is_on(ent_id):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -102,10 +103,10 @@ def reload_core_config(hass):
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Setup general services related to Home Assistant."""
|
||||
"""Set up general services related to Home Assistant."""
|
||||
@asyncio.coroutine
|
||||
def async_handle_turn_service(service):
|
||||
"""Method to handle calls to homeassistant.turn_on/off."""
|
||||
"""Handle calls to homeassistant.turn_on/off."""
|
||||
entity_ids = extract_entity_ids(hass, service)
|
||||
|
||||
# Generic turn on/off method requires entity id
|
||||
@@ -161,10 +162,9 @@ def async_setup(hass, config):
|
||||
return
|
||||
|
||||
if errors:
|
||||
notif = get_component('persistent_notification')
|
||||
_LOGGER.error(errors)
|
||||
notif.async_create(
|
||||
hass, "Config error. See dev-info panel for details.",
|
||||
hass.components.persistent_notification.async_create(
|
||||
"Config error. See dev-info panel for details.",
|
||||
"Config validating", "{0}.check_config".format(ha.DOMAIN))
|
||||
return
|
||||
|
||||
|
75
homeassistant/components/abode.py
Normal file
75
homeassistant/components/abode.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
This component provides basic support for Abode Home Security system.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_NAME
|
||||
|
||||
REQUIREMENTS = ['abodepy==0.7.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
DOMAIN = 'abode'
|
||||
DEFAULT_NAME = 'Abode'
|
||||
DATA_ABODE = 'data_abode'
|
||||
DEFAULT_ENTITY_NAMESPACE = 'abode'
|
||||
|
||||
NOTIFICATION_ID = 'abode_notification'
|
||||
NOTIFICATION_TITLE = 'Abode Security Setup'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up Abode component."""
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
try:
|
||||
data = AbodeData(username, password)
|
||||
hass.data[DATA_ABODE] = data
|
||||
|
||||
for component in ['binary_sensor', 'alarm_control_panel']:
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Abode: %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)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AbodeData:
|
||||
"""Shared Abode data."""
|
||||
|
||||
def __init__(self, username, password):
|
||||
"""Initialize Abode oject."""
|
||||
import abodepy
|
||||
|
||||
self.abode = abodepy.Abode(username, password)
|
||||
self.devices = self.abode.get_devices()
|
||||
|
||||
_LOGGER.debug("Abode Security set up with %s devices",
|
||||
len(self.devices))
|
@@ -13,8 +13,10 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_NIGHT)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@@ -30,6 +32,7 @@ SERVICE_TO_METHOD = {
|
||||
SERVICE_ALARM_DISARM: 'alarm_disarm',
|
||||
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
|
||||
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
|
||||
SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night',
|
||||
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
|
||||
}
|
||||
|
||||
@@ -44,6 +47,7 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_disarm(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for disarm."""
|
||||
data = {}
|
||||
@@ -55,6 +59,7 @@ def alarm_disarm(hass, code=None, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_home(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm home."""
|
||||
data = {}
|
||||
@@ -66,6 +71,7 @@ def alarm_arm_home(hass, code=None, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_away(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm away."""
|
||||
data = {}
|
||||
@@ -77,6 +83,19 @@ def alarm_arm_away(hass, code=None, entity_id=None):
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_night(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm night."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_trigger(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for trigger."""
|
||||
data = {}
|
||||
@@ -123,8 +142,8 @@ def async_setup(hass, config):
|
||||
if update_tasks:
|
||||
yield from asyncio.wait(update_tasks, loop=hass.loop)
|
||||
|
||||
descriptions = yield from hass.loop.run_in_executor(
|
||||
None, load_yaml_config_file, os.path.join(
|
||||
descriptions = yield from hass.async_add_job(
|
||||
load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
for service in SERVICE_TO_METHOD:
|
||||
@@ -158,8 +177,7 @@ class AlarmControlPanel(Entity):
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.loop.run_in_executor(
|
||||
None, self.alarm_disarm, code)
|
||||
return self.hass.async_add_job(self.alarm_disarm, code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
@@ -170,8 +188,7 @@ class AlarmControlPanel(Entity):
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.loop.run_in_executor(
|
||||
None, self.alarm_arm_home, code)
|
||||
return self.hass.async_add_job(self.alarm_arm_home, code)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
@@ -182,8 +199,18 @@ class AlarmControlPanel(Entity):
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.loop.run_in_executor(
|
||||
None, self.alarm_arm_away, code)
|
||||
return self.hass.async_add_job(self.alarm_arm_away, code)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(self.alarm_arm_night, code)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command."""
|
||||
@@ -194,8 +221,7 @@ class AlarmControlPanel(Entity):
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.loop.run_in_executor(
|
||||
None, self.alarm_trigger, code)
|
||||
return self.hass.async_add_job(self.alarm_trigger, code)
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
|
82
homeassistant/components/alarm_control_panel/abode.py
Normal file
82
homeassistant/components/alarm_control_panel/abode.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""
|
||||
This component provides HA alarm_control_panel support for Abode System.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (DATA_ABODE, DEFAULT_NAME)
|
||||
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ICON = 'mdi:security'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
data = hass.data.get(DATA_ABODE)
|
||||
|
||||
add_devices([AbodeAlarm(hass, data, data.abode.get_alarm())])
|
||||
|
||||
|
||||
class AbodeAlarm(alarm.AlarmControlPanel):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
def __init__(self, hass, data, device):
|
||||
"""Initialize the alarm control panel."""
|
||||
super(AbodeAlarm, self).__init__()
|
||||
self._device = device
|
||||
self._name = "{0}".format(DEFAULT_NAME)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self._device.mode == "standby":
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif self._device.mode == "away":
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
elif self._device.mode == "home":
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
else:
|
||||
state = None
|
||||
return state
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self._device.set_standby()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
self._device.set_home()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
self._device.set_away()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def update(self):
|
||||
"""Update the device state."""
|
||||
self._device.refresh()
|
@@ -25,7 +25,7 @@ DEPENDENCIES = ['alarmdecoder']
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Perform the setup for AlarmDecoder alarm panels."""
|
||||
"""Set up for AlarmDecoder alarm panels."""
|
||||
_LOGGER.debug("AlarmDecoderAlarmPanel: setup")
|
||||
|
||||
device = AlarmDecoderAlarmPanel("Alarm Panel", hass)
|
||||
@@ -44,7 +44,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
self._name = name
|
||||
self._state = STATE_UNKNOWN
|
||||
|
||||
_LOGGER.debug("AlarmDecoderAlarm: Setting up panel")
|
||||
_LOGGER.debug("Setting up panel")
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
@@ -78,12 +78,12 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Regex for code format or None if no code is required."""
|
||||
"""Return the regex for code format or None if no code is required."""
|
||||
return '^\\d{4,6}$'
|
||||
|
||||
@property
|
||||
@@ -94,26 +94,23 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: %s", code)
|
||||
_LOGGER.debug("alarm_disarm: %s", code)
|
||||
if code:
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_disarm: sending %s1",
|
||||
str(code))
|
||||
_LOGGER.debug("alarm_disarm: sending %s1", str(code))
|
||||
self.hass.data[DATA_AD].send("{!s}1".format(code))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: %s", code)
|
||||
_LOGGER.debug("alarm_arm_away: %s", code)
|
||||
if code:
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_away: sending %s2",
|
||||
str(code))
|
||||
_LOGGER.debug("alarm_arm_away: sending %s2", str(code))
|
||||
self.hass.data[DATA_AD].send("{!s}2".format(code))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: %s", code)
|
||||
_LOGGER.debug("alarm_arm_home: %s", code)
|
||||
if code:
|
||||
_LOGGER.debug("AlarmDecoderAlarm::alarm_arm_home: sending %s3",
|
||||
str(code))
|
||||
_LOGGER.debug("alarm_arm_home: sending %s3", str(code))
|
||||
self.hass.data[DATA_AD].send("{!s}3".format(code))
|
||||
|
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
|
||||
Interfaces with Alarm.com alarm control panels.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
@@ -33,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup a Alarm.com control panel."""
|
||||
"""Set up a Alarm.com control panel."""
|
||||
name = config.get(CONF_NAME)
|
||||
code = config.get(CONF_CODE)
|
||||
username = config.get(CONF_USERNAME)
|
||||
@@ -93,8 +92,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
elif self._alarm.state.lower() == 'armed away':
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
return STATE_UNKNOWN
|
||||
return STATE_UNKNOWN
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
|
@@ -51,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
"""Represents the Concord232-based alarm panel."""
|
||||
"""Representation of the Concord232-based alarm panel."""
|
||||
|
||||
def __init__(self, hass, url, name):
|
||||
"""Initialize the Concord232 alarm panel."""
|
||||
@@ -79,7 +79,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""The characters if code is defined."""
|
||||
"""Return the characters if code is defined."""
|
||||
return '[0-9]{4}([0-9]{2})?'
|
||||
|
||||
@property
|
||||
@@ -117,7 +117,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
self._alarm.arm('home')
|
||||
self._alarm.arm('stay')
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
|
@@ -8,7 +8,7 @@ import homeassistant.components.alarm_control_panel.manual as manual
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Demo alarm control panel platform."""
|
||||
"""Set up the Demo alarm control panel platform."""
|
||||
add_devices([
|
||||
manual.ManualAlarm(hass, 'Alarm', '1234', 5, 10, False),
|
||||
])
|
||||
|
182
homeassistant/components/alarm_control_panel/egardia.py
Normal file
182
homeassistant/components/alarm_control_panel/egardia.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""
|
||||
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 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)
|
||||
|
||||
REQUIREMENTS = ['pythonegardia==1.0.18']
|
||||
|
||||
_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'
|
||||
|
||||
DEFAULT_NAME = 'Egardia'
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_REPORT_SERVER_ENABLED = False
|
||||
DEFAULT_REPORT_SERVER_PORT = 85
|
||||
DOMAIN = 'egardia'
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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_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
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
egardiasystem = egardiadevice.EgardiaDevice(
|
||||
host, port, username, password, '')
|
||||
except requests.exceptions.RequestException:
|
||||
raise exc.PlatformNotReady()
|
||||
except egardiadevice.UnauthorizedError:
|
||||
_LOGGER.error("Unable to authorize. Wrong password or username")
|
||||
return False
|
||||
|
||||
add_devices([EgardiaAlarm(
|
||||
name, egardiasystem, hass, rs_enabled, rs_port, rs_codes)], True)
|
||||
|
||||
|
||||
class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Egardia alarm."""
|
||||
|
||||
def __init__(self, name, egardiasystem, hass, rs_enabled=False,
|
||||
rs_port=None, rs_codes=None):
|
||||
"""Initialize object."""
|
||||
self._name = name
|
||||
self._egardiasystem = egardiasystem
|
||||
self._status = STATE_UNKNOWN
|
||||
self._rs_enabled = rs_enabled
|
||||
self._rs_port = rs_port
|
||||
self._hass = hass
|
||||
|
||||
if rs_codes is not None:
|
||||
self._rs_codes = rs_codes[0]
|
||||
else:
|
||||
self._rs_codes = rs_codes
|
||||
|
||||
if self._rs_enabled:
|
||||
self.listen_to_system_status()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._status
|
||||
|
||||
def handle_system_status_event(self, event):
|
||||
"""Handle egardia_system_status_event."""
|
||||
if event.data.get('status') is not None:
|
||||
statuscode = event.data.get('status')
|
||||
status = self.lookupstatusfromcode(statuscode)
|
||||
self.parsestatus(status)
|
||||
|
||||
def listen_to_system_status(self):
|
||||
"""Subscribe to egardia_system_status event."""
|
||||
self._hass.bus.listen(
|
||||
'egardia_system_status', self.handle_system_status_event)
|
||||
|
||||
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
|
||||
return status
|
||||
|
||||
def parsestatus(self, status):
|
||||
"""Parse the status."""
|
||||
newstatus = ([v for k, v in STATES.items()
|
||||
if status.upper() == k][0])
|
||||
self._status = newstatus
|
||||
|
||||
def update(self):
|
||||
"""Update the alarm status."""
|
||||
status = self._egardiasystem.getstate()
|
||||
self.parsestatus(status)
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_disarm()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending disarm command: %s", err)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_arm_home()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending arm home command: %s", err)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
try:
|
||||
self._egardiasystem.alarm_arm_away()
|
||||
except requests.exceptions.RequestException as err:
|
||||
_LOGGER.error("Egardia device exception occurred when "
|
||||
"sending arm away command: %s", err)
|
@@ -70,8 +70,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
device.async_alarm_keypress(keypress)
|
||||
|
||||
# Register Envisalink specific services
|
||||
descriptions = yield from hass.loop.run_in_executor(
|
||||
None, load_yaml_config_file, os.path.join(
|
||||
descriptions = yield from hass.async_add_job(
|
||||
load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.async_register(
|
||||
@@ -104,7 +104,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
|
||||
@callback
|
||||
def _update_callback(self, partition):
|
||||
"""Update HA state, if needed."""
|
||||
"""Update Home Assistant state, if needed."""
|
||||
if partition is None or int(partition) == self._partition_number:
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@@ -113,8 +113,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
"""Regex for code format or None if no code is required."""
|
||||
if self._code:
|
||||
return None
|
||||
else:
|
||||
return '^\\d{4,6}$'
|
||||
return '^\\d{4,6}$'
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@@ -12,9 +12,10 @@ 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_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME,
|
||||
CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
|
||||
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
|
||||
CONF_DISARM_AFTER_TRIGGER)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
@@ -39,7 +40,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the manual alarm platform."""
|
||||
"""Set up the manual alarm platform."""
|
||||
add_devices([ManualAlarm(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
@@ -52,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
class ManualAlarm(alarm.AlarmControlPanel):
|
||||
"""
|
||||
Represents an alarm status.
|
||||
Representation of an alarm status.
|
||||
|
||||
When armed, will be pending for 'pending_time', after that armed.
|
||||
When triggered, will be pending for 'trigger_time'. After that will be
|
||||
@@ -62,7 +63,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def __init__(self, hass, name, code, pending_time,
|
||||
trigger_time, disarm_after_trigger):
|
||||
"""Initalize the manual alarm panel."""
|
||||
"""Init the manual alarm panel."""
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
@@ -75,7 +76,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
"""Return the plling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
@@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self._state in (STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY) and \
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_NIGHT) and \
|
||||
self._pending_time and self._state_ts + self._pending_time > \
|
||||
dt_util.utcnow():
|
||||
return STATE_ALARM_PENDING
|
||||
@@ -99,8 +101,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._trigger_time) < dt_util.utcnow():
|
||||
if self._disarm_after_trigger:
|
||||
return STATE_ALARM_DISARMED
|
||||
else:
|
||||
return self._pre_trigger_state
|
||||
return self._pre_trigger_state
|
||||
|
||||
return self._state
|
||||
|
||||
@@ -146,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT):
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_NIGHT
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._pending_time:
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command. No code needed."""
|
||||
self._pre_trigger_state = self._state
|
||||
@@ -166,5 +181,5 @@ class ManualAlarm(alarm.AlarmControlPanel):
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Invalid code given for %s', state)
|
||||
_LOGGER.warning("Invalid code given for %s", state)
|
||||
return check
|
||||
|
235
homeassistant/components/alarm_control_panel/manual_mqtt.py
Normal file
235
homeassistant/components/alarm_control_panel/manual_mqtt.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
Support for manual alarms controllable via MQTT.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import datetime
|
||||
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_DISARMED,
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM,
|
||||
CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME,
|
||||
CONF_DISARM_AFTER_TRIGGER)
|
||||
import homeassistant.components.mqtt as mqtt
|
||||
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.core import callback
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
|
||||
CONF_PAYLOAD_DISARM = 'payload_disarm'
|
||||
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
|
||||
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
|
||||
|
||||
DEFAULT_ALARM_NAME = 'HA Alarm'
|
||||
DEFAULT_PENDING_TIME = 60
|
||||
DEFAULT_TRIGGER_TIME = 120
|
||||
DEFAULT_DISARM_AFTER_TRIGGER = False
|
||||
DEFAULT_ARM_AWAY = 'ARM_AWAY'
|
||||
DEFAULT_ARM_HOME = 'ARM_HOME'
|
||||
DEFAULT_DISARM = 'DISARM'
|
||||
|
||||
DEPENDENCIES = ['mqtt']
|
||||
|
||||
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_PLATFORM): 'manual_mqtt',
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
|
||||
vol.Optional(CONF_CODE): cv.string,
|
||||
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=0)),
|
||||
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
|
||||
vol.All(vol.Coerce(int), vol.Range(min=1)),
|
||||
vol.Optional(CONF_DISARM_AFTER_TRIGGER,
|
||||
default=DEFAULT_DISARM_AFTER_TRIGGER): cv.boolean,
|
||||
vol.Required(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
|
||||
vol.Required(mqtt.CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
|
||||
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,
|
||||
})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the manual MQTT alarm platform."""
|
||||
add_devices([ManualMQTTAlarm(
|
||||
hass,
|
||||
config[CONF_NAME],
|
||||
config.get(CONF_CODE),
|
||||
config.get(CONF_PENDING_TIME, DEFAULT_PENDING_TIME),
|
||||
config.get(CONF_TRIGGER_TIME, DEFAULT_TRIGGER_TIME),
|
||||
config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
|
||||
config.get(mqtt.CONF_STATE_TOPIC),
|
||||
config.get(mqtt.CONF_COMMAND_TOPIC),
|
||||
config.get(mqtt.CONF_QOS),
|
||||
config.get(CONF_PAYLOAD_DISARM),
|
||||
config.get(CONF_PAYLOAD_ARM_HOME),
|
||||
config.get(CONF_PAYLOAD_ARM_AWAY))])
|
||||
|
||||
|
||||
class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
"""
|
||||
Representation of an alarm status.
|
||||
|
||||
When armed, will be pending for 'pending_time', after that armed.
|
||||
When triggered, will be pending for 'trigger_time'. After that will be
|
||||
triggered for 'trigger_time', after that we return to the previous state
|
||||
or disarm if `disarm_after_trigger` is true.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, name, code, pending_time,
|
||||
trigger_time, disarm_after_trigger,
|
||||
state_topic, command_topic, qos,
|
||||
payload_disarm, payload_arm_home, payload_arm_away):
|
||||
"""Init the manual MQTT alarm panel."""
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._code = str(code) if code else None
|
||||
self._pending_time = datetime.timedelta(seconds=pending_time)
|
||||
self._trigger_time = datetime.timedelta(seconds=trigger_time)
|
||||
self._disarm_after_trigger = disarm_after_trigger
|
||||
self._pre_trigger_state = self._state
|
||||
self._state_ts = None
|
||||
|
||||
self._state_topic = state_topic
|
||||
self._command_topic = command_topic
|
||||
self._qos = qos
|
||||
self._payload_disarm = payload_disarm
|
||||
self._payload_arm_home = payload_arm_home
|
||||
self._payload_arm_away = payload_arm_away
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
if self._state in (STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY) and \
|
||||
self._pending_time and self._state_ts + self._pending_time > \
|
||||
dt_util.utcnow():
|
||||
return STATE_ALARM_PENDING
|
||||
|
||||
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
|
||||
if self._state_ts + self._pending_time > dt_util.utcnow():
|
||||
return STATE_ALARM_PENDING
|
||||
elif (self._state_ts + self._pending_time +
|
||||
self._trigger_time) < dt_util.utcnow():
|
||||
if self._disarm_after_trigger:
|
||||
return STATE_ALARM_DISARMED
|
||||
return self._pre_trigger_state
|
||||
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""One or more characters."""
|
||||
return None if self._code is None else '.+'
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, STATE_ALARM_DISARMED):
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._pending_time:
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
|
||||
return
|
||||
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._pending_time:
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
def alarm_trigger(self, code=None):
|
||||
"""Send alarm trigger command. No code needed."""
|
||||
self._pre_trigger_state = self._state
|
||||
self._state = STATE_ALARM_TRIGGERED
|
||||
self._state_ts = dt_util.utcnow()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
if self._trigger_time:
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time)
|
||||
|
||||
track_point_in_time(
|
||||
self._hass, self.async_update_ha_state,
|
||||
self._state_ts + self._pending_time + self._trigger_time)
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning("Invalid code given for %s", state)
|
||||
return check
|
||||
|
||||
def async_added_to_hass(self):
|
||||
"""Subscribe mqtt events.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
async_track_state_change(
|
||||
self.hass, self.entity_id, self._async_state_changed_listener
|
||||
)
|
||||
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
"""Run when new MQTT message has been received."""
|
||||
if payload == self._payload_disarm:
|
||||
self.async_alarm_disarm(self._code)
|
||||
elif payload == self._payload_arm_home:
|
||||
self.async_alarm_arm_home(self._code)
|
||||
elif payload == self._payload_arm_away:
|
||||
self.async_alarm_arm_away(self._code)
|
||||
else:
|
||||
_LOGGER.warning("Received unexpected payload: %s", payload)
|
||||
return
|
||||
|
||||
return mqtt.async_subscribe(
|
||||
self.hass, self._command_topic, message_received, self._qos)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_state_changed_listener(self, entity_id, old_state, new_state):
|
||||
"""Publish state change to MQTT."""
|
||||
mqtt.async_publish(self.hass, self._state_topic, new_state.state,
|
||||
self._qos, True)
|
@@ -45,7 +45,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup the MQTT platform."""
|
||||
"""Set up the MQTT Alarm Control Panel platform."""
|
||||
async_add_devices([MqttAlarm(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
@@ -62,7 +62,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
|
||||
|
||||
def __init__(self, name, state_topic, command_topic, qos, payload_disarm,
|
||||
payload_arm_home, payload_arm_away, code):
|
||||
"""Initalize the MQTT alarm panel."""
|
||||
"""Init the MQTT Alarm Control Panel."""
|
||||
self._state = STATE_UNKNOWN
|
||||
self._name = name
|
||||
self._state_topic = state_topic
|
||||
@@ -80,11 +80,11 @@ class MqttAlarm(alarm.AlarmControlPanel):
|
||||
"""
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
"""Run when new MQTT message has been received."""
|
||||
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
|
||||
STATE_ALARM_TRIGGERED):
|
||||
_LOGGER.warning('Received unexpected payload: %s', payload)
|
||||
_LOGGER.warning("Received unexpected payload: %s", payload)
|
||||
return
|
||||
self._state = payload
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
||||
|
@@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup nx584 platform."""
|
||||
"""Set up the nx584 platform."""
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
@@ -42,15 +42,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
try:
|
||||
add_devices([NX584Alarm(hass, url, name)])
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
|
||||
_LOGGER.error("Unable to connect to NX584: %s", str(ex))
|
||||
return False
|
||||
|
||||
|
||||
class NX584Alarm(alarm.AlarmControlPanel):
|
||||
"""Represents the NX584-based alarm panel."""
|
||||
"""Representation of a NX584-based alarm panel."""
|
||||
|
||||
def __init__(self, hass, url, name):
|
||||
"""Initalize the nx584 alarm panel."""
|
||||
"""Init the nx584 alarm panel."""
|
||||
from nx584 import client
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
@@ -69,7 +69,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""The characters if code is defined."""
|
||||
"""Return che characters if code is defined."""
|
||||
return '[0-9]{4}([0-9]{2})?'
|
||||
|
||||
@property
|
||||
@@ -83,20 +83,19 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
part = self._alarm.list_partitions()[0]
|
||||
zones = self._alarm.list_zones()
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
|
||||
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
|
||||
dict(host=self._url, reason=ex))
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
except IndexError:
|
||||
_LOGGER.error('nx584 reports no partitions')
|
||||
_LOGGER.error("nx584 reports no partitions")
|
||||
self._state = STATE_UNKNOWN
|
||||
zones = []
|
||||
|
||||
bypassed = False
|
||||
for zone in zones:
|
||||
if zone['bypassed']:
|
||||
_LOGGER.debug('Zone %(zone)s is bypassed, '
|
||||
'assuming HOME',
|
||||
_LOGGER.debug("Zone %(zone)s is bypassed, assuming HOME",
|
||||
dict(zone=zone['number']))
|
||||
bypassed = True
|
||||
break
|
||||
|
@@ -31,6 +31,17 @@ alarm_arm_away:
|
||||
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
|
||||
|
||||
|
@@ -15,9 +15,8 @@ from homeassistant.const import (
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.loader as loader
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.2']
|
||||
REQUIREMENTS = ['simplisafe-python==1.0.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,7 +41,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
persistent_notification = loader.get_component('persistent_notification')
|
||||
simplisafe = SimpliSafeApiInterface()
|
||||
status = simplisafe.set_credentials(username, password)
|
||||
if status:
|
||||
@@ -53,8 +51,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
else:
|
||||
message = 'Failed to log into SimpliSafe. Check credentials.'
|
||||
_LOGGER.error(message)
|
||||
persistent_notification.create(
|
||||
hass, message,
|
||||
hass.components.persistent_notification.create(
|
||||
message,
|
||||
title=NOTIFICATION_TITLE,
|
||||
notification_id=NOTIFICATION_ID)
|
||||
return False
|
||||
@@ -80,8 +78,7 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
"""Return the name of the device."""
|
||||
if self._name is not None:
|
||||
return self._name
|
||||
else:
|
||||
return 'Alarm {}'.format(self.simplisafe.location_id())
|
||||
return 'Alarm {}'.format(self.simplisafe.location_id())
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
@@ -92,11 +89,11 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
status = self.simplisafe.state()
|
||||
if status == 'Off':
|
||||
if status == 'off':
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif status == 'Home':
|
||||
elif status == 'home':
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif status == 'Away':
|
||||
elif status == 'away':
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
@@ -123,25 +120,25 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
self.simplisafe.set_state('off')
|
||||
_LOGGER.info('SimpliSafe alarm disarming')
|
||||
_LOGGER.info("SimpliSafe alarm disarming")
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
self.simplisafe.set_state('home')
|
||||
_LOGGER.info('SimpliSafe alarm arming home')
|
||||
_LOGGER.info("SimpliSafe alarm arming home")
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
self.simplisafe.set_state('away')
|
||||
_LOGGER.info('SimpliSafe alarm arming away')
|
||||
_LOGGER.info("SimpliSafe alarm arming away")
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
"""Validate given code."""
|
||||
check = self._code is None or code == self._code
|
||||
if not check:
|
||||
_LOGGER.warning('Wrong code entered for %s', state)
|
||||
_LOGGER.warning("Wrong code entered for %s", state)
|
||||
return check
|
||||
|
96
homeassistant/components/alarm_control_panel/spc.py
Normal file
96
homeassistant/components/alarm_control_panel/spc.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Support for Vanderbilt (formerly Siemens) SPC alarm systems.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.spc/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.spc import (
|
||||
SpcWebGateway, ATTR_DISCOVER_AREAS, DATA_API, DATA_REGISTRY)
|
||||
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}
|
||||
|
||||
|
||||
def _get_alarm_state(spc_mode):
|
||||
return SPC_AREA_MODE_TO_STATE.get(spc_mode, STATE_UNKNOWN)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the SPC alarm control panel platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_AREAS] is None):
|
||||
return
|
||||
|
||||
entities = [SpcAlarm(hass=hass,
|
||||
area_id=area['id'],
|
||||
name=area['name'],
|
||||
state=_get_alarm_state(area['mode']))
|
||||
for area in discovery_info[ATTR_DISCOVER_AREAS]]
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class SpcAlarm(alarm.AlarmControlPanel):
|
||||
"""Represents the SPC alarm panel."""
|
||||
|
||||
def __init__(self, hass, area_id, name, state):
|
||||
"""Initialize the SPC alarm panel."""
|
||||
self._hass = hass
|
||||
self._area_id = area_id
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._api = hass.data[DATA_API]
|
||||
|
||||
hass.data[DATA_REGISTRY].register_alarm_device(area_id, self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update_from_spc(self, state):
|
||||
"""Update the alarm panel with a new state."""
|
||||
self._state = state
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_UNSET)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_PART_SET)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
yield from self._api.send_area_command(
|
||||
self._area_id, SpcWebGateway.AREA_COMMAND_SET)
|
@@ -1,17 +1,22 @@
|
||||
"""Interfaces with TotalConnect alarm control panels."""
|
||||
"""
|
||||
Interfaces with TotalConnect alarm control panels.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.totalconnect/
|
||||
"""
|
||||
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, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
|
||||
CONF_NAME)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
|
||||
|
||||
REQUIREMENTS = ['total_connect_client==0.7']
|
||||
REQUIREMENTS = ['total_connect_client==0.11']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup a TotalConnect control panel."""
|
||||
"""Set up a TotalConnect control panel."""
|
||||
name = config.get(CONF_NAME)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
@@ -41,13 +46,13 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
"""Initialize the TotalConnect status."""
|
||||
from total_connect_client import TotalConnectClient
|
||||
|
||||
_LOGGER.debug('Setting up TotalConnect...')
|
||||
_LOGGER.debug("Setting up TotalConnect...")
|
||||
self._name = name
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._state = STATE_UNKNOWN
|
||||
self._client = TotalConnectClient.TotalConnectClient(username,
|
||||
password)
|
||||
self._client = TotalConnectClient.TotalConnectClient(
|
||||
username, password)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -69,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif status == self._client.ARMED_AWAY:
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
elif status == self._client.ARMED_STAY_NIGHT:
|
||||
state = STATE_ALARM_ARMED_NIGHT
|
||||
elif status == self._client.ARMING:
|
||||
state = STATE_ALARM_ARMING
|
||||
elif status == self._client.DISARMING:
|
||||
state = STATE_ALARM_DISARMING
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
|
||||
@@ -85,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel):
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
self._client.arm_away()
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send arm night command."""
|
||||
self._client.arm_stay_night()
|
||||
|
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.verisure/
|
||||
"""
|
||||
import logging
|
||||
from time import sleep
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.verisure import HUB as hub
|
||||
@@ -17,23 +18,32 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Verisure platform."""
|
||||
"""Set up the Verisure platform."""
|
||||
alarms = []
|
||||
if int(hub.config.get(CONF_ALARM, 1)):
|
||||
hub.update_alarms()
|
||||
alarms.extend([
|
||||
VerisureAlarm(value.id)
|
||||
for value in hub.alarm_status.values()
|
||||
])
|
||||
hub.update_overview()
|
||||
alarms.append(VerisureAlarm())
|
||||
add_devices(alarms)
|
||||
|
||||
|
||||
class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
"""Represent a Verisure alarm status."""
|
||||
def set_arm_state(state, code=None):
|
||||
"""Send set arm state command."""
|
||||
transaction_id = hub.session.set_arm_state(code, state)[
|
||||
'armStateChangeTransactionId']
|
||||
_LOGGER.info('verisure set arm state %s', state)
|
||||
transaction = {}
|
||||
while 'result' not in transaction:
|
||||
sleep(0.5)
|
||||
transaction = hub.session.get_arm_state_transaction(transaction_id)
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
hub.update_overview(no_throttle=True)
|
||||
|
||||
def __init__(self, device_id):
|
||||
|
||||
class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
"""Representation of a Verisure alarm status."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initalize the Verisure alarm panel."""
|
||||
self._id = device_id
|
||||
self._state = STATE_UNKNOWN
|
||||
self._digits = hub.config.get(CONF_CODE_DIGITS)
|
||||
self._changed_by = None
|
||||
@@ -41,58 +51,45 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return 'Alarm {}'.format(self._id)
|
||||
return '{} alarm'.format(hub.session.installations[0]['alias'])
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return hub.available
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""The code format as regex."""
|
||||
"""Return the code format as regex."""
|
||||
return '^\\d{%s}$' % self._digits
|
||||
|
||||
@property
|
||||
def changed_by(self):
|
||||
"""Last change triggered by."""
|
||||
"""Return the last change triggered by."""
|
||||
return self._changed_by
|
||||
|
||||
def update(self):
|
||||
"""Update alarm status."""
|
||||
hub.update_alarms()
|
||||
|
||||
if hub.alarm_status[self._id].status == 'unarmed':
|
||||
hub.update_overview()
|
||||
status = hub.get_first("$.armState.statusType")
|
||||
if status == 'DISARMED':
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif hub.alarm_status[self._id].status == 'armedhome':
|
||||
elif status == 'ARMED_HOME':
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif hub.alarm_status[self._id].status == 'armed':
|
||||
elif status == 'ARMED_AWAY':
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
elif hub.alarm_status[self._id].status != 'pending':
|
||||
_LOGGER.error(
|
||||
'Unknown alarm state %s',
|
||||
hub.alarm_status[self._id].status)
|
||||
self._changed_by = hub.alarm_status[self._id].name
|
||||
elif status != 'PENDING':
|
||||
_LOGGER.error('Unknown alarm state %s', status)
|
||||
self._changed_by = hub.get_first("$.armState.name")
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
hub.my_pages.alarm.set(code, 'DISARMED')
|
||||
_LOGGER.info('verisure alarm disarming')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
set_arm_state('DISARMED', code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
hub.my_pages.alarm.set(code, 'ARMED_HOME')
|
||||
_LOGGER.info('verisure alarm arming home')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
set_arm_state('ARMED_HOME', code)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
hub.my_pages.alarm.set(code, 'ARMED_AWAY')
|
||||
_LOGGER.info('verisure alarm arming away')
|
||||
hub.my_pages.alarm.wait_while_pending()
|
||||
set_arm_state('ARMED_AWAY', code)
|
||||
|
@@ -4,6 +4,7 @@ Interfaces with Wink Cameras.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.wink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
@@ -16,11 +17,12 @@ from homeassistant.components.wink import WinkDevice, DOMAIN
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['wink']
|
||||
|
||||
STATE_ALARM_PRIVACY = 'Private'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Wink platform."""
|
||||
"""Set up the Wink platform."""
|
||||
import pywink
|
||||
|
||||
for camera in pywink.get_cameras():
|
||||
@@ -37,9 +39,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
"""Representation a Wink camera alarm."""
|
||||
|
||||
def __init__(self, wink, hass):
|
||||
"""Initialize the Wink alarm."""
|
||||
super().__init__(wink, hass)
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
"""Callback when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@@ -8,15 +8,14 @@ import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
REQUIREMENTS = ['alarmdecoder==0.12.1.0']
|
||||
REQUIREMENTS = ['alarmdecoder==0.12.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,19 +23,16 @@ DOMAIN = 'alarmdecoder'
|
||||
|
||||
DATA_AD = 'alarmdecoder'
|
||||
|
||||
|
||||
CONF_DEVICE = 'device'
|
||||
CONF_DEVICE_TYPE = 'type'
|
||||
CONF_DEVICE_HOST = 'host'
|
||||
CONF_DEVICE_PORT = 'port'
|
||||
CONF_DEVICE_PATH = 'path'
|
||||
CONF_DEVICE_BAUD = 'baudrate'
|
||||
|
||||
CONF_ZONES = 'zones'
|
||||
CONF_DEVICE_HOST = 'host'
|
||||
CONF_DEVICE_PATH = 'path'
|
||||
CONF_DEVICE_PORT = 'port'
|
||||
CONF_DEVICE_TYPE = 'type'
|
||||
CONF_PANEL_DISPLAY = 'panel_display'
|
||||
CONF_ZONE_NAME = 'name'
|
||||
CONF_ZONE_TYPE = 'type'
|
||||
|
||||
CONF_PANEL_DISPLAY = 'panel_display'
|
||||
CONF_ZONES = 'zones'
|
||||
|
||||
DEFAULT_DEVICE_TYPE = 'socket'
|
||||
DEFAULT_DEVICE_HOST = 'localhost'
|
||||
@@ -87,7 +83,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Common setup for AlarmDecoder devices."""
|
||||
"""Set up for the AlarmDecoder devices."""
|
||||
from alarmdecoder import AlarmDecoder
|
||||
from alarmdecoder.devices import (SocketDevice, SerialDevice, USBDevice)
|
||||
|
||||
@@ -106,28 +102,28 @@ def async_setup(hass, config):
|
||||
sync_connect = asyncio.Future(loop=hass.loop)
|
||||
|
||||
def handle_open(device):
|
||||
"""Callback for a successful connection."""
|
||||
_LOGGER.info("Established a connection with the alarmdecoder.")
|
||||
"""Handle the successful connection."""
|
||||
_LOGGER.info("Established a connection with the alarmdecoder")
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder)
|
||||
sync_connect.set_result(True)
|
||||
|
||||
@callback
|
||||
def stop_alarmdecoder(event):
|
||||
"""Callback to handle shutdown alarmdecoder."""
|
||||
_LOGGER.debug("Shutting down alarmdecoder.")
|
||||
"""Handle the shutdown of AlarmDecoder."""
|
||||
_LOGGER.debug("Shutting down alarmdecoder")
|
||||
controller.close()
|
||||
|
||||
@callback
|
||||
def handle_message(sender, message):
|
||||
"""Callback to handle message from alarmdecoder."""
|
||||
"""Handle message from AlarmDecoder."""
|
||||
async_dispatcher_send(hass, SIGNAL_PANEL_MESSAGE, message)
|
||||
|
||||
def zone_fault_callback(sender, zone):
|
||||
"""Callback to handle zone fault from alarmdecoder."""
|
||||
"""Handle zone fault from AlarmDecoder."""
|
||||
async_dispatcher_send(hass, SIGNAL_ZONE_FAULT, zone)
|
||||
|
||||
def zone_restore_callback(sender, zone):
|
||||
"""Callback to handle zone restore from alarmdecoder."""
|
||||
"""Handle zone restore from AlarmDecoder."""
|
||||
async_dispatcher_send(hass, SIGNAL_ZONE_RESTORE, zone)
|
||||
|
||||
controller = False
|
||||
@@ -157,15 +153,16 @@ def async_setup(hass, config):
|
||||
if not result:
|
||||
return False
|
||||
|
||||
hass.async_add_job(async_load_platform(hass, 'alarm_control_panel', DOMAIN,
|
||||
conf, config))
|
||||
hass.async_add_job(
|
||||
async_load_platform(hass, 'alarm_control_panel', DOMAIN, conf,
|
||||
config))
|
||||
|
||||
if zones:
|
||||
hass.async_add_job(async_load_platform(
|
||||
hass, 'binary_sensor', DOMAIN, {CONF_ZONES: zones}, config))
|
||||
|
||||
if display:
|
||||
hass.async_add_job(async_load_platform(hass, 'sensor', DOMAIN,
|
||||
conf, config))
|
||||
hass.async_add_job(async_load_platform(
|
||||
hass, 'sensor', DOMAIN, conf, config))
|
||||
|
||||
return True
|
||||
|
@@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = 'alert'
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
CONF_DONE_MESSAGE = 'done_message'
|
||||
CONF_CAN_ACK = 'can_acknowledge'
|
||||
CONF_NOTIFIERS = 'notifiers'
|
||||
CONF_REPEAT = 'repeat'
|
||||
@@ -35,6 +36,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.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,15 +123,15 @@ def async_setup(hass, config):
|
||||
# Setup alerts
|
||||
for entity_id, alert in alerts.items():
|
||||
entity = Alert(hass, entity_id,
|
||||
alert[CONF_NAME], alert[CONF_ENTITY_ID],
|
||||
alert[CONF_STATE], alert[CONF_REPEAT],
|
||||
alert[CONF_SKIP_FIRST], alert[CONF_NOTIFIERS],
|
||||
alert[CONF_CAN_ACK])
|
||||
alert[CONF_NAME], alert[CONF_DONE_MESSAGE],
|
||||
alert[CONF_ENTITY_ID], alert[CONF_STATE],
|
||||
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
|
||||
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])
|
||||
all_alerts[entity.entity_id] = entity
|
||||
|
||||
# Read descriptions
|
||||
descriptions = yield from hass.loop.run_in_executor(
|
||||
None, load_yaml_config_file, os.path.join(
|
||||
descriptions = yield from hass.async_add_job(
|
||||
load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml'))
|
||||
descriptions = descriptions.get(DOMAIN, {})
|
||||
|
||||
@@ -154,8 +156,8 @@ def async_setup(hass, config):
|
||||
class Alert(ToggleEntity):
|
||||
"""Representation of an alert."""
|
||||
|
||||
def __init__(self, hass, entity_id, name, watched_entity_id, state,
|
||||
repeat, skip_first, notifiers, can_ack):
|
||||
def __init__(self, hass, entity_id, name, done_message, watched_entity_id,
|
||||
state, repeat, skip_first, notifiers, can_ack):
|
||||
"""Initialize the alert."""
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
@@ -163,6 +165,7 @@ class Alert(ToggleEntity):
|
||||
self._skip_first = skip_first
|
||||
self._notifiers = notifiers
|
||||
self._can_ack = can_ack
|
||||
self._done_message = done_message
|
||||
|
||||
self._delay = [timedelta(minutes=val) for val in repeat]
|
||||
self._next_delay = 0
|
||||
@@ -170,6 +173,7 @@ class Alert(ToggleEntity):
|
||||
self._firing = False
|
||||
self._ack = False
|
||||
self._cancel = None
|
||||
self._send_done_message = False
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
|
||||
|
||||
event.async_track_state_change(
|
||||
@@ -230,6 +234,8 @@ class Alert(ToggleEntity):
|
||||
self._cancel()
|
||||
self._ack = False
|
||||
self._firing = False
|
||||
if self._done_message and self._send_done_message:
|
||||
yield from self._notify_done_message()
|
||||
self.hass.async_add_job(self.async_update_ha_state)
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -249,20 +255,30 @@ class Alert(ToggleEntity):
|
||||
|
||||
if not self._ack:
|
||||
_LOGGER.info("Alerting: %s", self._name)
|
||||
self._send_done_message = True
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._name})
|
||||
yield from self._schedule_notify()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self):
|
||||
def _notify_done_message(self, *args):
|
||||
"""Send notification of complete alert."""
|
||||
_LOGGER.info("Alerting: %s", self._done_message)
|
||||
self._send_done_message = False
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._done_message})
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
"""Async Unacknowledge alert."""
|
||||
_LOGGER.debug("Reset Alert: %s", self._name)
|
||||
self._ack = False
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self):
|
||||
def async_turn_off(self, **kwargs):
|
||||
"""Async Acknowledge alert."""
|
||||
_LOGGER.debug("Acknowledged Alert: %s", self._name)
|
||||
self._ack = True
|
||||
|
@@ -15,9 +15,8 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import HTTP_BAD_REQUEST
|
||||
from homeassistant.helpers import template, script, config_validation as cv
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.helpers import intent, template, config_validation as cv
|
||||
from homeassistant.components import http
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,7 +35,6 @@ CONF_TEXT = 'text'
|
||||
|
||||
CONF_FLASH_BRIEFINGS = 'flash_briefings'
|
||||
CONF_UID = 'uid'
|
||||
CONF_DATE = 'date'
|
||||
CONF_TITLE = 'title'
|
||||
CONF_AUDIO = 'audio'
|
||||
CONF_TEXT = 'text'
|
||||
@@ -62,6 +60,12 @@ class SpeechType(enum.Enum):
|
||||
ssml = "SSML"
|
||||
|
||||
|
||||
SPEECH_MAPPINGS = {
|
||||
'plain': SpeechType.plaintext,
|
||||
'ssml': SpeechType.ssml,
|
||||
}
|
||||
|
||||
|
||||
class CardType(enum.Enum):
|
||||
"""The Alexa card types."""
|
||||
|
||||
@@ -71,24 +75,9 @@ class CardType(enum.Enum):
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
CONF_INTENTS: {
|
||||
cv.string: {
|
||||
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_CARD): {
|
||||
vol.Required(CONF_TYPE): cv.enum(CardType),
|
||||
vol.Required(CONF_TITLE): cv.template,
|
||||
vol.Required(CONF_CONTENT): cv.template,
|
||||
},
|
||||
vol.Optional(CONF_SPEECH): {
|
||||
vol.Required(CONF_TYPE): cv.enum(SpeechType),
|
||||
vol.Required(CONF_TEXT): cv.template,
|
||||
}
|
||||
}
|
||||
},
|
||||
CONF_FLASH_BRIEFINGS: {
|
||||
cv.string: vol.All(cv.ensure_list, [{
|
||||
vol.Required(CONF_UID, default=str(uuid.uuid4())): cv.string,
|
||||
vol.Optional(CONF_DATE, default=datetime.utcnow()): cv.string,
|
||||
vol.Required(CONF_TITLE): cv.template,
|
||||
vol.Optional(CONF_AUDIO): cv.template,
|
||||
vol.Required(CONF_TEXT, default=""): cv.template,
|
||||
@@ -99,40 +88,27 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Activate Alexa component."""
|
||||
intents = config[DOMAIN].get(CONF_INTENTS, {})
|
||||
flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {})
|
||||
|
||||
hass.http.register_view(AlexaIntentsView(hass, intents))
|
||||
hass.http.register_view(AlexaIntentsView)
|
||||
hass.http.register_view(AlexaFlashBriefingView(hass, flash_briefings))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AlexaIntentsView(HomeAssistantView):
|
||||
class AlexaIntentsView(http.HomeAssistantView):
|
||||
"""Handle Alexa requests."""
|
||||
|
||||
url = INTENTS_API_ENDPOINT
|
||||
name = 'api:alexa'
|
||||
|
||||
def __init__(self, hass, intents):
|
||||
"""Initialize Alexa view."""
|
||||
super().__init__()
|
||||
|
||||
intents = copy.deepcopy(intents)
|
||||
template.attach(hass, intents)
|
||||
|
||||
for name, intent in intents.items():
|
||||
if CONF_ACTION in intent:
|
||||
intent[CONF_ACTION] = script.Script(
|
||||
hass, intent[CONF_ACTION], "Alexa intent {}".format(name))
|
||||
|
||||
self.intents = intents
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
"""Handle Alexa."""
|
||||
hass = request.app['hass']
|
||||
data = yield from request.json()
|
||||
|
||||
_LOGGER.debug('Received Alexa request: %s', data)
|
||||
@@ -149,53 +125,61 @@ class AlexaIntentsView(HomeAssistantView):
|
||||
if req_type == 'SessionEndedRequest':
|
||||
return None
|
||||
|
||||
intent = req.get('intent')
|
||||
response = AlexaResponse(request.app['hass'], intent)
|
||||
alexa_intent_info = req.get('intent')
|
||||
alexa_response = AlexaResponse(hass, alexa_intent_info)
|
||||
|
||||
if req_type == 'LaunchRequest':
|
||||
response.add_speech(
|
||||
SpeechType.plaintext,
|
||||
"Hello, and welcome to the future. How may I help?")
|
||||
return self.json(response)
|
||||
|
||||
if req_type != 'IntentRequest':
|
||||
if req_type != 'IntentRequest' and req_type != 'LaunchRequest':
|
||||
_LOGGER.warning('Received unsupported request: %s', req_type)
|
||||
return self.json_message(
|
||||
'Received unsupported request: {}'.format(req_type),
|
||||
HTTP_BAD_REQUEST)
|
||||
|
||||
intent_name = intent['name']
|
||||
config = self.intents.get(intent_name)
|
||||
if req_type == 'LaunchRequest':
|
||||
intent_name = data.get('session', {}) \
|
||||
.get('application', {}) \
|
||||
.get('applicationId')
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
|
||||
if config is None:
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, intent_name,
|
||||
{key: {'value': value} for key, value
|
||||
in alexa_response.variables.items()})
|
||||
except intent.UnknownIntent as err:
|
||||
_LOGGER.warning('Received unknown intent %s', intent_name)
|
||||
response.add_speech(
|
||||
alexa_response.add_speech(
|
||||
SpeechType.plaintext,
|
||||
"This intent is not yet configured within Home Assistant.")
|
||||
return self.json(response)
|
||||
return self.json(alexa_response)
|
||||
|
||||
speech = config.get(CONF_SPEECH)
|
||||
card = config.get(CONF_CARD)
|
||||
action = config.get(CONF_ACTION)
|
||||
except intent.InvalidSlotInfo as err:
|
||||
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
|
||||
return self.json_message('Invalid slot data received',
|
||||
HTTP_BAD_REQUEST)
|
||||
except intent.IntentError:
|
||||
_LOGGER.exception('Error handling request for %s', intent_name)
|
||||
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
|
||||
|
||||
if action is not None:
|
||||
yield from action.async_run(response.variables)
|
||||
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
|
||||
if intent_speech in intent_response.speech:
|
||||
alexa_response.add_speech(
|
||||
alexa_speech,
|
||||
intent_response.speech[intent_speech]['speech'])
|
||||
break
|
||||
|
||||
# pylint: disable=unsubscriptable-object
|
||||
if speech is not None:
|
||||
response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
|
||||
if 'simple' in intent_response.card:
|
||||
alexa_response.add_card(
|
||||
CardType.simple, intent_response.card['simple']['title'],
|
||||
intent_response.card['simple']['content'])
|
||||
|
||||
if card is not None:
|
||||
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
|
||||
card[CONF_CONTENT])
|
||||
|
||||
return self.json(response)
|
||||
return self.json(alexa_response)
|
||||
|
||||
|
||||
class AlexaResponse(object):
|
||||
"""Help generating the response for Alexa."""
|
||||
|
||||
def __init__(self, hass, intent=None):
|
||||
def __init__(self, hass, intent_info):
|
||||
"""Initialize the response."""
|
||||
self.hass = hass
|
||||
self.speech = None
|
||||
@@ -204,8 +188,9 @@ class AlexaResponse(object):
|
||||
self.session_attributes = {}
|
||||
self.should_end_session = True
|
||||
self.variables = {}
|
||||
if intent is not None and 'slots' in intent:
|
||||
for key, value in intent['slots'].items():
|
||||
# Intent is None if request was a LaunchRequest or SessionEndedRequest
|
||||
if intent_info is not None:
|
||||
for key, value in intent_info.get('slots', {}).items():
|
||||
if 'value' in value:
|
||||
underscored_key = key.replace('.', '_')
|
||||
self.variables[underscored_key] = value['value']
|
||||
@@ -222,8 +207,8 @@ class AlexaResponse(object):
|
||||
self.card = card
|
||||
return
|
||||
|
||||
card["title"] = title.async_render(self.variables)
|
||||
card["content"] = content.async_render(self.variables)
|
||||
card["title"] = title
|
||||
card["content"] = content
|
||||
self.card = card
|
||||
|
||||
def add_speech(self, speech_type, text):
|
||||
@@ -232,9 +217,6 @@ class AlexaResponse(object):
|
||||
|
||||
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
|
||||
|
||||
if isinstance(text, template.Template):
|
||||
text = text.async_render(self.variables)
|
||||
|
||||
self.speech = {
|
||||
'type': speech_type.value,
|
||||
key: text
|
||||
@@ -275,7 +257,7 @@ class AlexaResponse(object):
|
||||
}
|
||||
|
||||
|
||||
class AlexaFlashBriefingView(HomeAssistantView):
|
||||
class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
"""Handle Alexa Flash Briefing skill requests."""
|
||||
|
||||
url = FLASH_BRIEFINGS_API_ENDPOINT
|
||||
@@ -331,10 +313,7 @@ class AlexaFlashBriefingView(HomeAssistantView):
|
||||
else:
|
||||
output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL)
|
||||
|
||||
if isinstance(item[CONF_DATE], str):
|
||||
item[CONF_DATE] = dt_util.parse_datetime(item[CONF_DATE])
|
||||
|
||||
output[ATTR_UPDATE_DATE] = item[CONF_DATE].strftime(DATE_FORMAT)
|
||||
output[ATTR_UPDATE_DATE] = datetime.now().strftime(DATE_FORMAT)
|
||||
|
||||
briefing.append(output)
|
||||
|
||||
|
147
homeassistant/components/amcrest.py
Normal file
147
homeassistant/components/amcrest.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
This component provides basic support for Amcrest IP cameras.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/amcrest/
|
||||
"""
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_SENSORS, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION)
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['amcrest==1.2.1']
|
||||
DEPENDENCIES = ['ffmpeg']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_AUTHENTICATION = 'authentication'
|
||||
CONF_RESOLUTION = 'resolution'
|
||||
CONF_STREAM_SOURCE = 'stream_source'
|
||||
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
|
||||
|
||||
DEFAULT_NAME = 'Amcrest Camera'
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_RESOLUTION = 'high'
|
||||
DEFAULT_STREAM_SOURCE = 'snapshot'
|
||||
TIMEOUT = 10
|
||||
|
||||
DATA_AMCREST = 'amcrest'
|
||||
DOMAIN = 'amcrest'
|
||||
|
||||
NOTIFICATION_ID = 'amcrest_notification'
|
||||
NOTIFICATION_TITLE = 'Amcrest Camera Setup'
|
||||
|
||||
RESOLUTION_LIST = {
|
||||
'high': 0,
|
||||
'low': 1,
|
||||
}
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
AUTHENTICATION_LIST = {
|
||||
'basic': 'basic'
|
||||
}
|
||||
|
||||
STREAM_SOURCE_LIST = {
|
||||
'mjpeg': 0,
|
||||
'snapshot': 1,
|
||||
'rtsp': 2,
|
||||
}
|
||||
|
||||
# Sensor types are defined like: Name, units, icon
|
||||
SENSORS = {
|
||||
'motion_detector': ['Motion Detected', None, 'mdi:run'],
|
||||
'sdcard': ['SD Used', '%', 'mdi:sd'],
|
||||
'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'],
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
|
||||
vol.All(vol.In(AUTHENTICATION_LIST)),
|
||||
vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION):
|
||||
vol.All(vol.In(RESOLUTION_LIST)),
|
||||
vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE):
|
||||
vol.All(vol.In(STREAM_SOURCE_LIST)),
|
||||
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.All(cv.ensure_list, [vol.In(SENSORS)]),
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the Amcrest IP Camera component."""
|
||||
from amcrest import AmcrestCamera
|
||||
|
||||
amcrest_cams = config[DOMAIN]
|
||||
|
||||
for device in amcrest_cams:
|
||||
camera = AmcrestCamera(device.get(CONF_HOST),
|
||||
device.get(CONF_PORT),
|
||||
device.get(CONF_USERNAME),
|
||||
device.get(CONF_PASSWORD)).camera
|
||||
try:
|
||||
camera.current_time
|
||||
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Amcrest camera: %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)
|
||||
return False
|
||||
|
||||
ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS)
|
||||
name = device.get(CONF_NAME)
|
||||
resolution = RESOLUTION_LIST[device.get(CONF_RESOLUTION)]
|
||||
sensors = device.get(CONF_SENSORS)
|
||||
stream_source = STREAM_SOURCE_LIST[device.get(CONF_STREAM_SOURCE)]
|
||||
|
||||
username = device.get(CONF_USERNAME)
|
||||
password = device.get(CONF_PASSWORD)
|
||||
|
||||
# currently aiohttp only works with basic authentication
|
||||
# only valid for mjpeg streaming
|
||||
if username is not None and password is not None:
|
||||
if device.get(CONF_AUTHENTICATION) == HTTP_BASIC_AUTHENTICATION:
|
||||
authentication = aiohttp.BasicAuth(username, password)
|
||||
else:
|
||||
authentication = None
|
||||
|
||||
discovery.load_platform(
|
||||
hass, 'camera', DOMAIN, {
|
||||
'device': camera,
|
||||
CONF_AUTHENTICATION: authentication,
|
||||
CONF_FFMPEG_ARGUMENTS: ffmpeg_arguments,
|
||||
CONF_NAME: name,
|
||||
CONF_RESOLUTION: resolution,
|
||||
CONF_STREAM_SOURCE: stream_source,
|
||||
}, config)
|
||||
|
||||
if sensors:
|
||||
discovery.load_platform(
|
||||
hass, 'sensor', DOMAIN, {
|
||||
'device': camera,
|
||||
CONF_NAME: name,
|
||||
CONF_SENSORS: sensors,
|
||||
}, config)
|
||||
|
||||
return True
|
@@ -13,7 +13,7 @@ from homeassistant.const import (CONF_HOST, CONF_PORT)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
REQUIREMENTS = ['apcaccess==0.0.4']
|
||||
REQUIREMENTS = ['apcaccess==0.0.13']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@@ -83,7 +83,7 @@ class APIEventStream(HomeAssistantView):
|
||||
stop_obj = object()
|
||||
to_write = asyncio.Queue(loop=hass.loop)
|
||||
|
||||
restrict = request.GET.get('restrict')
|
||||
restrict = request.query.get('restrict')
|
||||
if restrict:
|
||||
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
|
||||
|
||||
@@ -198,8 +198,7 @@ class APIEntityStateView(HomeAssistantView):
|
||||
state = request.app['hass'].states.get(entity_id)
|
||||
if state:
|
||||
return self.json(state)
|
||||
else:
|
||||
return self.json_message('Entity not found', HTTP_NOT_FOUND)
|
||||
return self.json_message('Entity not found', HTTP_NOT_FOUND)
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request, entity_id):
|
||||
@@ -213,7 +212,7 @@ class APIEntityStateView(HomeAssistantView):
|
||||
|
||||
new_state = data.get('state')
|
||||
|
||||
if not new_state:
|
||||
if new_state is None:
|
||||
return self.json_message('No state specified', HTTP_BAD_REQUEST)
|
||||
|
||||
attributes = data.get('attributes')
|
||||
@@ -237,8 +236,7 @@ class APIEntityStateView(HomeAssistantView):
|
||||
"""Remove entity."""
|
||||
if request.app['hass'].states.async_remove(entity_id):
|
||||
return self.json_message('Entity removed')
|
||||
else:
|
||||
return self.json_message('Entity not found', HTTP_NOT_FOUND)
|
||||
return self.json_message('Entity not found', HTTP_NOT_FOUND)
|
||||
|
||||
|
||||
class APIEventListenersView(HomeAssistantView):
|
||||
|
@@ -5,13 +5,12 @@ For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/apiai/
|
||||
"""
|
||||
import asyncio
|
||||
import copy
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import PROJECT_NAME, HTTP_BAD_REQUEST
|
||||
from homeassistant.helpers import template, script, config_validation as cv
|
||||
from homeassistant.helpers import intent, template
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -29,24 +28,14 @@ DOMAIN = 'apiai'
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: {
|
||||
CONF_INTENTS: {
|
||||
cv.string: {
|
||||
vol.Optional(CONF_SPEECH): cv.template,
|
||||
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
vol.Optional(CONF_ASYNC_ACTION,
|
||||
default=DEFAULT_CONF_ASYNC_ACTION): cv.boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
DOMAIN: {}
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Activate API.AI component."""
|
||||
intents = config[DOMAIN].get(CONF_INTENTS, {})
|
||||
|
||||
hass.http.register_view(ApiaiIntentsView(hass, intents))
|
||||
hass.http.register_view(ApiaiIntentsView)
|
||||
|
||||
return True
|
||||
|
||||
@@ -57,24 +46,10 @@ class ApiaiIntentsView(HomeAssistantView):
|
||||
url = INTENTS_API_ENDPOINT
|
||||
name = 'api:apiai'
|
||||
|
||||
def __init__(self, hass, intents):
|
||||
"""Initialize API.AI view."""
|
||||
super().__init__()
|
||||
|
||||
self.hass = hass
|
||||
intents = copy.deepcopy(intents)
|
||||
template.attach(hass, intents)
|
||||
|
||||
for name, intent in intents.items():
|
||||
if CONF_ACTION in intent:
|
||||
intent[CONF_ACTION] = script.Script(
|
||||
hass, intent[CONF_ACTION], "Apiai intent {}".format(name))
|
||||
|
||||
self.intents = intents
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
"""Handle API.AI."""
|
||||
hass = request.app['hass']
|
||||
data = yield from request.json()
|
||||
|
||||
_LOGGER.debug("Received api.ai request: %s", data)
|
||||
@@ -91,55 +66,41 @@ class ApiaiIntentsView(HomeAssistantView):
|
||||
if action_incomplete:
|
||||
return None
|
||||
|
||||
# use intent to no mix HASS actions with this parameter
|
||||
intent = req.get('action')
|
||||
action = req.get('action')
|
||||
parameters = req.get('parameters')
|
||||
# contexts = req.get('contexts')
|
||||
response = ApiaiResponse(parameters)
|
||||
apiai_response = ApiaiResponse(parameters)
|
||||
|
||||
# Default Welcome Intent
|
||||
# Maybe is better to handle this in api.ai directly?
|
||||
#
|
||||
# if intent == 'input.welcome':
|
||||
# response.add_speech(
|
||||
# "Hello, and welcome to the future. How may I help?")
|
||||
# return self.json(response)
|
||||
|
||||
if intent == "":
|
||||
if action == "":
|
||||
_LOGGER.warning("Received intent with empty action")
|
||||
response.add_speech(
|
||||
apiai_response.add_speech(
|
||||
"You have not defined an action in your api.ai intent.")
|
||||
return self.json(response)
|
||||
return self.json(apiai_response)
|
||||
|
||||
config = self.intents.get(intent)
|
||||
try:
|
||||
intent_response = yield from intent.async_handle(
|
||||
hass, DOMAIN, action,
|
||||
{key: {'value': value} for key, value
|
||||
in parameters.items()})
|
||||
|
||||
if config is None:
|
||||
_LOGGER.warning("Received unknown intent %s", intent)
|
||||
response.add_speech(
|
||||
"Intent '%s' is not yet configured within Home Assistant." %
|
||||
intent)
|
||||
return self.json(response)
|
||||
except intent.UnknownIntent as err:
|
||||
_LOGGER.warning('Received unknown intent %s', action)
|
||||
apiai_response.add_speech(
|
||||
"This intent is not yet configured within Home Assistant.")
|
||||
return self.json(apiai_response)
|
||||
|
||||
speech = config.get(CONF_SPEECH)
|
||||
action = config.get(CONF_ACTION)
|
||||
async_action = config.get(CONF_ASYNC_ACTION)
|
||||
except intent.InvalidSlotInfo as err:
|
||||
_LOGGER.error('Received invalid slot data: %s', err)
|
||||
return self.json_message('Invalid slot data received',
|
||||
HTTP_BAD_REQUEST)
|
||||
except intent.IntentError:
|
||||
_LOGGER.exception('Error handling request for %s', action)
|
||||
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
|
||||
|
||||
if action is not None:
|
||||
# API.AI expects a response in less than 5s
|
||||
if async_action:
|
||||
# Do not wait for the action to be executed.
|
||||
# Needed if the action will take longer than 5s to execute
|
||||
self.hass.async_add_job(action.async_run(response.parameters))
|
||||
else:
|
||||
# Wait for the action to be executed so we can use results to
|
||||
# render the answer
|
||||
yield from action.async_run(response.parameters)
|
||||
if 'plain' in intent_response.speech:
|
||||
apiai_response.add_speech(
|
||||
intent_response.speech['plain']['speech'])
|
||||
|
||||
# pylint: disable=unsubscriptable-object
|
||||
if speech is not None:
|
||||
response.add_speech(speech)
|
||||
|
||||
return self.json(response)
|
||||
return self.json(apiai_response)
|
||||
|
||||
|
||||
class ApiaiResponse(object):
|
||||
|
253
homeassistant/components/apple_tv.py
Normal file
253
homeassistant/components/apple_tv.py
Normal file
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Support for Apple TV.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/apple_tv/
|
||||
"""
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (CONF_HOST, CONF_NAME, ATTR_ENTITY_ID)
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.components.discovery import SERVICE_APPLE_TV
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['pyatv==0.3.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'apple_tv'
|
||||
|
||||
SERVICE_SCAN = 'apple_tv_scan'
|
||||
SERVICE_AUTHENTICATE = 'apple_tv_authenticate'
|
||||
|
||||
ATTR_ATV = 'atv'
|
||||
ATTR_POWER = 'power'
|
||||
|
||||
CONF_LOGIN_ID = 'login_id'
|
||||
CONF_START_OFF = 'start_off'
|
||||
CONF_CREDENTIALS = 'credentials'
|
||||
|
||||
DEFAULT_NAME = 'Apple TV'
|
||||
|
||||
DATA_APPLE_TV = 'data_apple_tv'
|
||||
DATA_ENTITIES = 'data_apple_tv_entities'
|
||||
|
||||
KEY_CONFIG = 'apple_tv_configuring'
|
||||
|
||||
NOTIFICATION_AUTH_ID = 'apple_tv_auth_notification'
|
||||
NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication'
|
||||
NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification'
|
||||
NOTIFICATION_SCAN_TITLE = 'Apple TV Scan'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_LOGIN_ID): 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
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
# Currently no attributes but it might change later
|
||||
APPLE_TV_SCAN_SCHEMA = vol.Schema({})
|
||||
|
||||
APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({
|
||||
ATTR_ENTITY_ID: cv.entity_ids,
|
||||
})
|
||||
|
||||
|
||||
def request_configuration(hass, config, atv, credentials):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = hass.components.configurator
|
||||
|
||||
@asyncio.coroutine
|
||||
def configuration_callback(callback_data):
|
||||
"""Handle the submitted configuration."""
|
||||
from pyatv import exceptions
|
||||
pin = callback_data.get('pin')
|
||||
|
||||
try:
|
||||
yield from atv.airplay.finish_authentication(pin)
|
||||
hass.components.persistent_notification.async_create(
|
||||
'Authentication succeeded!<br /><br />Add the following '
|
||||
'to credentials: in your apple_tv configuration:<br /><br />'
|
||||
'{0}'.format(credentials),
|
||||
title=NOTIFICATION_AUTH_TITLE,
|
||||
notification_id=NOTIFICATION_AUTH_ID)
|
||||
except exceptions.DeviceAuthenticationError as ex:
|
||||
hass.components.persistent_notification.async_create(
|
||||
'Authentication failed! Did you enter correct PIN?<br /><br />'
|
||||
'Details: {0}'.format(ex),
|
||||
title=NOTIFICATION_AUTH_TITLE,
|
||||
notification_id=NOTIFICATION_AUTH_ID)
|
||||
|
||||
hass.async_add_job(configurator.request_done, instance)
|
||||
|
||||
instance = configurator.request_config(
|
||||
'Apple TV Authentication', configuration_callback,
|
||||
description='Please enter PIN code shown on screen.',
|
||||
submit_caption='Confirm',
|
||||
fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}]
|
||||
)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def scan_for_apple_tvs(hass):
|
||||
"""Scan for devices and present a notification of the ones found."""
|
||||
import pyatv
|
||||
atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
|
||||
|
||||
devices = []
|
||||
for atv in atvs:
|
||||
login_id = atv.login_id
|
||||
if login_id is None:
|
||||
login_id = 'Home Sharing disabled'
|
||||
devices.append('Name: {0}<br />Host: {1}<br />Login ID: {2}'.format(
|
||||
atv.name, atv.address, login_id))
|
||||
|
||||
if not devices:
|
||||
devices = ['No device(s) found']
|
||||
|
||||
hass.components.persistent_notification.async_create(
|
||||
'The following devices were found:<br /><br />' +
|
||||
'<br /><br />'.join(devices),
|
||||
title=NOTIFICATION_SCAN_TITLE,
|
||||
notification_id=NOTIFICATION_SCAN_ID)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up the Apple TV component."""
|
||||
if DATA_APPLE_TV not in hass.data:
|
||||
hass.data[DATA_APPLE_TV] = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_service_handler(service):
|
||||
"""Handler for service calls."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
if entity_ids:
|
||||
devices = [device for device in hass.data[DATA_ENTITIES]
|
||||
if device.entity_id in entity_ids]
|
||||
else:
|
||||
devices = hass.data[DATA_ENTITIES]
|
||||
|
||||
for device in devices:
|
||||
atv = device.atv
|
||||
if service.service == SERVICE_AUTHENTICATE:
|
||||
credentials = yield from atv.airplay.generate_credentials()
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
_LOGGER.debug('Generated new credentials: %s', credentials)
|
||||
yield from atv.airplay.start_authentication()
|
||||
hass.async_add_job(request_configuration,
|
||||
hass, config, atv, credentials)
|
||||
elif service.service == SERVICE_SCAN:
|
||||
hass.async_add_job(scan_for_apple_tvs, hass)
|
||||
|
||||
@asyncio.coroutine
|
||||
def atv_discovered(service, info):
|
||||
"""Setup an Apple TV that was auto discovered."""
|
||||
yield from _setup_atv(hass, {
|
||||
CONF_NAME: info['name'],
|
||||
CONF_HOST: info['host'],
|
||||
CONF_LOGIN_ID: info['properties']['hG'],
|
||||
CONF_START_OFF: False
|
||||
})
|
||||
|
||||
discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered)
|
||||
|
||||
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
descriptions = yield from hass.async_add_job(
|
||||
load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SCAN, async_service_handler,
|
||||
descriptions.get(SERVICE_SCAN),
|
||||
schema=APPLE_TV_SCAN_SCHEMA)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_AUTHENTICATE, async_service_handler,
|
||||
descriptions.get(SERVICE_AUTHENTICATE),
|
||||
schema=APPLE_TV_AUTHENTICATE_SCHEMA)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _setup_atv(hass, atv_config):
|
||||
"""Setup an Apple TV."""
|
||||
import pyatv
|
||||
name = atv_config.get(CONF_NAME)
|
||||
host = atv_config.get(CONF_HOST)
|
||||
login_id = atv_config.get(CONF_LOGIN_ID)
|
||||
start_off = atv_config.get(CONF_START_OFF)
|
||||
credentials = atv_config.get(CONF_CREDENTIALS)
|
||||
|
||||
if host in hass.data[DATA_APPLE_TV]:
|
||||
return
|
||||
|
||||
details = pyatv.AppleTVDevice(name, host, login_id)
|
||||
session = async_get_clientsession(hass)
|
||||
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
|
||||
if credentials:
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
|
||||
power = AppleTVPowerManager(hass, atv, start_off)
|
||||
hass.data[DATA_APPLE_TV][host] = {
|
||||
ATTR_ATV: atv,
|
||||
ATTR_POWER: power
|
||||
}
|
||||
|
||||
hass.async_add_job(discovery.async_load_platform(
|
||||
hass, 'media_player', DOMAIN, atv_config))
|
||||
|
||||
hass.async_add_job(discovery.async_load_platform(
|
||||
hass, 'remote', DOMAIN, atv_config))
|
||||
|
||||
|
||||
class AppleTVPowerManager:
|
||||
"""Manager for global power management of an Apple TV.
|
||||
|
||||
An instance is used per device to share the same power state between
|
||||
several platforms.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, atv, is_off):
|
||||
"""Initialize power manager."""
|
||||
self.hass = hass
|
||||
self.atv = atv
|
||||
self.listeners = []
|
||||
self._is_on = not is_off
|
||||
|
||||
def init(self):
|
||||
"""Initialize power management."""
|
||||
if self._is_on:
|
||||
self.atv.push_updater.start()
|
||||
|
||||
@property
|
||||
def turned_on(self):
|
||||
"""If device is on or off."""
|
||||
return self._is_on
|
||||
|
||||
def set_power_on(self, value):
|
||||
"""Change if a device is on or off."""
|
||||
if value != self._is_on:
|
||||
self._is_on = value
|
||||
if not self._is_on:
|
||||
self.atv.push_updater.stop()
|
||||
else:
|
||||
self.atv.push_updater.start()
|
||||
|
||||
for listener in self.listeners:
|
||||
self.hass.async_add_job(listener.async_update_ha_state())
|
58
homeassistant/components/arlo.py
Normal file
58
homeassistant/components/arlo.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
This component provides basic support for Netgear Arlo IP cameras.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/arlo/
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
||||
|
||||
REQUIREMENTS = ['pyarlo==0.0.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_ATTRIBUTION = "Data provided by arlo.netgear.com"
|
||||
|
||||
DATA_ARLO = 'data_arlo'
|
||||
DEFAULT_BRAND = 'Netgear Arlo'
|
||||
DOMAIN = 'arlo'
|
||||
|
||||
NOTIFICATION_ID = 'arlo_notification'
|
||||
NOTIFICATION_TITLE = 'Arlo Camera Setup'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up an Arlo component."""
|
||||
conf = config[DOMAIN]
|
||||
username = conf.get(CONF_USERNAME)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
try:
|
||||
from pyarlo import PyArlo
|
||||
|
||||
arlo = PyArlo(username, password, preload=False)
|
||||
if not arlo.is_connected:
|
||||
return False
|
||||
hass.data[DATA_ARLO] = arlo
|
||||
except (ConnectTimeout, HTTPError) as ex:
|
||||
_LOGGER.error("Unable to connect to Netgar Arlo: %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)
|
||||
return False
|
||||
return True
|
82
homeassistant/components/asterisk_mbox.py
Normal file
82
homeassistant/components/asterisk_mbox.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Support for Asterisk Voicemail interface."""
|
||||
|
||||
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.core import callback
|
||||
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'
|
||||
|
||||
DOMAIN = 'asterisk_mbox'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PORT): int,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up for the Asterisk Voicemail box."""
|
||||
conf = config.get(DOMAIN)
|
||||
|
||||
host = conf.get(CONF_HOST)
|
||||
port = conf.get(CONF_PORT)
|
||||
password = conf.get(CONF_PASSWORD)
|
||||
|
||||
hass.data[DOMAIN] = AsteriskData(hass, host, port, password)
|
||||
|
||||
discovery.load_platform(hass, "mailbox", DOMAIN, {}, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AsteriskData(object):
|
||||
"""Store Asterisk mailbox data."""
|
||||
|
||||
def __init__(self, hass, host, port, password):
|
||||
"""Init the Asterisk data object."""
|
||||
from asterisk_mbox import Client as asteriskClient
|
||||
|
||||
self.hass = hass
|
||||
self.client = asteriskClient(host, port, password, self.handle_data)
|
||||
self.messages = []
|
||||
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages)
|
||||
|
||||
@callback
|
||||
def handle_data(self, command, msg):
|
||||
"""Handle changes to the mailbox."""
|
||||
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)
|
||||
|
||||
@callback
|
||||
def _request_messages(self):
|
||||
"""Handle changes to the mailbox."""
|
||||
_LOGGER.info("Requesting message list")
|
||||
self.client.messages()
|
@@ -13,10 +13,11 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
from homeassistant.core import CoreState
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant import config as conf_util
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START)
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import extract_domain_configs, script, condition
|
||||
@@ -28,6 +29,7 @@ from homeassistant.util.dt import utcnow
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DOMAIN = 'automation'
|
||||
DEPENDENCIES = ['group']
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
|
||||
@@ -81,6 +83,8 @@ _TRIGGER_SCHEMA = vol.All(
|
||||
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
# str on purpose
|
||||
CONF_ID: str,
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
|
||||
@@ -101,6 +105,7 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({
|
||||
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def is_on(hass, entity_id):
|
||||
"""
|
||||
Return true if specified automation entity_id is on.
|
||||
@@ -110,48 +115,59 @@ def is_on(hass, entity_id):
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_on(hass, entity_id=None):
|
||||
"""Turn on specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_off(hass, entity_id=None):
|
||||
"""Turn off specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def toggle(hass, entity_id=None):
|
||||
"""Toggle specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def trigger(hass, entity_id=None):
|
||||
"""Trigger specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def reload(hass):
|
||||
"""Reload the automation from config."""
|
||||
hass.services.call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def async_reload(hass):
|
||||
"""Reload the automation from config.
|
||||
|
||||
Returns a coroutine object.
|
||||
"""
|
||||
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Setup the automation."""
|
||||
"""Set up the automation."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
||||
group_name=GROUP_NAME_ALL_AUTOMATIONS)
|
||||
|
||||
success = yield from _async_process_config(hass, config, component)
|
||||
yield from _async_process_config(hass, config, component)
|
||||
|
||||
if not success:
|
||||
return False
|
||||
|
||||
descriptions = yield from hass.loop.run_in_executor(
|
||||
None, conf_util.load_yaml_config_file, os.path.join(
|
||||
descriptions = yield from hass.async_add_job(
|
||||
conf_util.load_yaml_config_file, os.path.join(
|
||||
os.path.dirname(__file__), 'services.yaml')
|
||||
)
|
||||
|
||||
@@ -221,9 +237,10 @@ def async_setup(hass, config):
|
||||
class AutomationEntity(ToggleEntity):
|
||||
"""Entity to show status of entity."""
|
||||
|
||||
def __init__(self, name, async_attach_triggers, cond_func, async_action,
|
||||
hidden, initial_state):
|
||||
def __init__(self, automation_id, name, async_attach_triggers, cond_func,
|
||||
async_action, hidden, initial_state):
|
||||
"""Initialize an automation entity."""
|
||||
self._id = automation_id
|
||||
self._name = name
|
||||
self._async_attach_triggers = async_attach_triggers
|
||||
self._async_detach_triggers = None
|
||||
@@ -346,6 +363,16 @@ class AutomationEntity(ToggleEntity):
|
||||
self.async_trigger)
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return automation attributes."""
|
||||
if self._id is None:
|
||||
return None
|
||||
|
||||
return {
|
||||
CONF_ID: self._id
|
||||
}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_process_config(hass, config, component):
|
||||
@@ -359,6 +386,7 @@ def _async_process_config(hass, config, component):
|
||||
conf = config[config_key]
|
||||
|
||||
for list_no, config_block in enumerate(conf):
|
||||
automation_id = config_block.get(CONF_ID)
|
||||
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
|
||||
list_no)
|
||||
|
||||
@@ -383,16 +411,14 @@ def _async_process_config(hass, config, component):
|
||||
config_block.get(CONF_TRIGGER, []), name
|
||||
)
|
||||
entity = AutomationEntity(
|
||||
name, async_attach_triggers, cond_func, action, hidden,
|
||||
initial_state)
|
||||
automation_id, name, async_attach_triggers, cond_func, action,
|
||||
hidden, initial_state)
|
||||
|
||||
entities.append(entity)
|
||||
|
||||
if entities:
|
||||
yield from component.async_add_entities(entities)
|
||||
|
||||
return len(entities) > 0
|
||||
|
||||
|
||||
def _async_get_action(hass, config, name):
|
||||
"""Return an action based on a configuration."""
|
||||
@@ -400,7 +426,7 @@ def _async_get_action(hass, config, name):
|
||||
|
||||
@asyncio.coroutine
|
||||
def action(entity_id, variables):
|
||||
"""Action to be executed."""
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.async_log_entry(
|
||||
hass, name, 'has been triggered', DOMAIN, entity_id)
|
||||
@@ -430,7 +456,7 @@ def _async_process_if(hass, config, p_config):
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_process_trigger(hass, config, trigger_configs, name, action):
|
||||
"""Setup the triggers.
|
||||
"""Set up the triggers.
|
||||
|
||||
This method is a coroutine.
|
||||
"""
|
||||
|
@@ -9,12 +9,12 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback, CoreState
|
||||
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
CONF_EVENT_TYPE = "event_type"
|
||||
CONF_EVENT_DATA = "event_data"
|
||||
CONF_EVENT_TYPE = 'event_type'
|
||||
CONF_EVENT_DATA = 'event_data'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,19 +31,6 @@ def async_trigger(hass, config, action):
|
||||
event_type = config.get(CONF_EVENT_TYPE)
|
||||
event_data = config.get(CONF_EVENT_DATA)
|
||||
|
||||
if (event_type == EVENT_HOMEASSISTANT_START and
|
||||
hass.state == CoreState.starting):
|
||||
_LOGGER.warning('Deprecation: Automations should not listen to event '
|
||||
"'homeassistant_start'. Use platform 'homeassistant' "
|
||||
'instead. Feature will be removed in 0.45')
|
||||
hass.async_run_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event': None,
|
||||
},
|
||||
})
|
||||
return lambda: None
|
||||
|
||||
@callback
|
||||
def handle_event(event):
|
||||
"""Listen for events and calls the action when data matches."""
|
||||
|
@@ -31,7 +31,7 @@ def async_trigger(hass, config, action):
|
||||
if event == EVENT_SHUTDOWN:
|
||||
@callback
|
||||
def hass_shutdown(event):
|
||||
"""Called when Home Assistant is shutting down."""
|
||||
"""Execute when Home Assistant is shutting down."""
|
||||
hass.async_run_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'homeassistant',
|
||||
|
@@ -14,11 +14,10 @@ from homeassistant.helpers.event import (
|
||||
async_track_state_change, async_track_point_in_utc_time)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
CONF_ENTITY_ID = "entity_id"
|
||||
CONF_FROM = "from"
|
||||
CONF_TO = "to"
|
||||
CONF_STATE = "state"
|
||||
CONF_FOR = "for"
|
||||
CONF_ENTITY_ID = 'entity_id'
|
||||
CONF_FROM = 'from'
|
||||
CONF_TO = 'to'
|
||||
CONF_FOR = 'for'
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(
|
||||
vol.Schema({
|
||||
@@ -27,11 +26,9 @@ TRIGGER_SCHEMA = vol.All(
|
||||
# These are str on purpose. Want to catch YAML conversions
|
||||
CONF_FROM: str,
|
||||
CONF_TO: str,
|
||||
CONF_STATE: str,
|
||||
CONF_FOR: vol.All(cv.time_period, cv.positive_timedelta),
|
||||
}),
|
||||
vol.Any(cv.key_dependency(CONF_FOR, CONF_TO),
|
||||
cv.key_dependency(CONF_FOR, CONF_STATE))
|
||||
cv.key_dependency(CONF_FOR, CONF_TO),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,10 +37,11 @@ def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
|
||||
to_state = config.get(CONF_TO, MATCH_ALL)
|
||||
time_delta = config.get(CONF_FOR)
|
||||
async_remove_state_for_cancel = None
|
||||
async_remove_state_for_listener = None
|
||||
match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL)
|
||||
|
||||
@callback
|
||||
def clear_listener():
|
||||
@@ -75,6 +73,11 @@ def async_trigger(hass, config, action):
|
||||
}
|
||||
})
|
||||
|
||||
# Ignore changes to state attributes if from/to is in use
|
||||
if (not match_all and from_s is not None and to_s is not None and
|
||||
from_s.last_changed == to_s.last_changed):
|
||||
return
|
||||
|
||||
if time_delta is None:
|
||||
call_action()
|
||||
return
|
||||
|
@@ -16,8 +16,6 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
DEPENDENCIES = ['sun']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
@@ -44,8 +42,6 @@ def async_trigger(hass, config, action):
|
||||
},
|
||||
})
|
||||
|
||||
# Do something to call action
|
||||
if event == SUN_EVENT_SUNRISE:
|
||||
return async_track_sunrise(hass, call_action, offset)
|
||||
else:
|
||||
return async_track_sunset(hass, call_action, offset)
|
||||
return async_track_sunset(hass, call_action, offset)
|
||||
|
@@ -10,32 +10,31 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_AFTER, CONF_PLATFORM
|
||||
from homeassistant.const import CONF_AT, CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
CONF_HOURS = "hours"
|
||||
CONF_MINUTES = "minutes"
|
||||
CONF_SECONDS = "seconds"
|
||||
CONF_HOURS = 'hours'
|
||||
CONF_MINUTES = 'minutes'
|
||||
CONF_SECONDS = 'seconds'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'time',
|
||||
CONF_AFTER: cv.time,
|
||||
CONF_AT: cv.time,
|
||||
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES,
|
||||
CONF_SECONDS, CONF_AFTER))
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AFTER in config:
|
||||
after = config.get(CONF_AFTER)
|
||||
hours, minutes, seconds = after.hour, after.minute, after.second
|
||||
if CONF_AT in config:
|
||||
at_time = config.get(CONF_AT)
|
||||
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
|
||||
else:
|
||||
hours = config.get(CONF_HOURS)
|
||||
minutes = config.get(CONF_MINUTES)
|
||||
|
@@ -14,8 +14,8 @@ from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers import (
|
||||
condition, config_validation as cv, location)
|
||||
|
||||
EVENT_ENTER = "enter"
|
||||
EVENT_LEAVE = "leave"
|
||||
EVENT_ENTER = 'enter'
|
||||
EVENT_LEAVE = 'leave'
|
||||
DEFAULT_EVENT = EVENT_ENTER
|
||||
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
|
371
homeassistant/components/axis.py
Normal file
371
homeassistant/components/axis.py
Normal file
@@ -0,0 +1,371 @@
|
||||
"""
|
||||
Support for Axis devices.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/axis/
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config import load_yaml_config_file
|
||||
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
|
||||
CONF_HOST, CONF_INCLUDE, CONF_NAME,
|
||||
CONF_PASSWORD, CONF_TRIGGER_TIME,
|
||||
CONF_USERNAME, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.components.discovery import SERVICE_AXIS
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
|
||||
REQUIREMENTS = ['axis==8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'axis'
|
||||
CONFIG_FILE = 'axis.conf'
|
||||
|
||||
AXIS_DEVICES = {}
|
||||
|
||||
EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound',
|
||||
'daynight', 'tampering', 'input']
|
||||
|
||||
PLATFORMS = ['camera']
|
||||
|
||||
AXIS_INCLUDE = EVENT_TYPES + PLATFORMS
|
||||
|
||||
AXIS_DEFAULT_HOST = '192.168.0.90'
|
||||
AXIS_DEFAULT_USERNAME = 'root'
|
||||
AXIS_DEFAULT_PASSWORD = 'pass'
|
||||
|
||||
DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_INCLUDE):
|
||||
vol.All(cv.ensure_list, [vol.In(AXIS_INCLUDE)]),
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string,
|
||||
vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int,
|
||||
vol.Optional(ATTR_LOCATION, default=''): cv.string,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: DEVICE_SCHEMA,
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SERVICE_VAPIX_CALL = 'vapix_call'
|
||||
SERVICE_VAPIX_CALL_RESPONSE = 'vapix_call_response'
|
||||
SERVICE_CGI = 'cgi'
|
||||
SERVICE_ACTION = 'action'
|
||||
SERVICE_PARAM = 'param'
|
||||
SERVICE_DEFAULT_CGI = 'param.cgi'
|
||||
SERVICE_DEFAULT_ACTION = 'update'
|
||||
|
||||
SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(SERVICE_PARAM): cv.string,
|
||||
vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string,
|
||||
vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def request_configuration(hass, name, host, serialnumber):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = hass.components.configurator
|
||||
|
||||
def configuration_callback(callback_data):
|
||||
"""Called when config is submitted."""
|
||||
if CONF_INCLUDE not in callback_data:
|
||||
configurator.notify_errors(request_id,
|
||||
"Functionality mandatory.")
|
||||
return False
|
||||
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
|
||||
callback_data[CONF_HOST] = host
|
||||
if CONF_NAME not in callback_data:
|
||||
callback_data[CONF_NAME] = name
|
||||
try:
|
||||
config = DEVICE_SCHEMA(callback_data)
|
||||
except vol.Invalid:
|
||||
configurator.notify_errors(request_id,
|
||||
"Bad input, please check spelling.")
|
||||
return False
|
||||
|
||||
if setup_device(hass, config):
|
||||
config_file = _read_config(hass)
|
||||
config_file[serialnumber] = dict(config)
|
||||
del config_file[serialnumber]['hass']
|
||||
_write_config(hass, config_file)
|
||||
configurator.request_done(request_id)
|
||||
else:
|
||||
configurator.notify_errors(request_id,
|
||||
"Failed to register, please try again.")
|
||||
return False
|
||||
|
||||
title = '{} ({})'.format(name, host)
|
||||
request_id = configurator.request_config(
|
||||
title, configuration_callback,
|
||||
description='Functionality: ' + str(AXIS_INCLUDE),
|
||||
entity_picture="/static/images/logo_axis.png",
|
||||
link_name='Axis platform documentation',
|
||||
link_url='https://home-assistant.io/components/axis/',
|
||||
submit_caption="Confirm",
|
||||
fields=[
|
||||
{'id': CONF_NAME,
|
||||
'name': "Device name",
|
||||
'type': 'text'},
|
||||
{'id': CONF_USERNAME,
|
||||
'name': "User name",
|
||||
'type': 'text'},
|
||||
{'id': CONF_PASSWORD,
|
||||
'name': 'Password',
|
||||
'type': 'password'},
|
||||
{'id': CONF_INCLUDE,
|
||||
'name': "Device functionality (space separated list)",
|
||||
'type': 'text'},
|
||||
{'id': ATTR_LOCATION,
|
||||
'name': "Physical location of device (optional)",
|
||||
'type': 'text'},
|
||||
{'id': CONF_TRIGGER_TIME,
|
||||
'name': "Sensor update interval (optional)",
|
||||
'type': 'number'},
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def setup(hass, base_config):
|
||||
"""Common setup for Axis devices."""
|
||||
def _shutdown(call): # pylint: disable=unused-argument
|
||||
"""Stop the metadatastream on shutdown."""
|
||||
for serialnumber, device in AXIS_DEVICES.items():
|
||||
_LOGGER.info("Stopping metadatastream for %s.", serialnumber)
|
||||
device.stop_metadatastream()
|
||||
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||
|
||||
def axis_device_discovered(service, discovery_info):
|
||||
"""Called when axis devices has been found."""
|
||||
host = discovery_info[CONF_HOST]
|
||||
name = discovery_info['hostname']
|
||||
serialnumber = discovery_info['properties']['macaddress']
|
||||
|
||||
if serialnumber not in AXIS_DEVICES:
|
||||
config_file = _read_config(hass)
|
||||
if serialnumber in config_file:
|
||||
# Device config saved to file
|
||||
try:
|
||||
config = DEVICE_SCHEMA(config_file[serialnumber])
|
||||
config[CONF_HOST] = host
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err)
|
||||
return False
|
||||
if not setup_device(hass, config):
|
||||
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
|
||||
else:
|
||||
# New device, create configuration request for UI
|
||||
request_configuration(hass, name, host, serialnumber)
|
||||
else:
|
||||
# Device already registered, but on a different IP
|
||||
device = AXIS_DEVICES[serialnumber]
|
||||
device.url = host
|
||||
async_dispatcher_send(hass,
|
||||
DOMAIN + '_' + device.name + '_new_ip',
|
||||
host)
|
||||
|
||||
# Register discovery service
|
||||
discovery.listen(hass, SERVICE_AXIS, axis_device_discovered)
|
||||
|
||||
if DOMAIN in base_config:
|
||||
for device in base_config[DOMAIN]:
|
||||
config = base_config[DOMAIN][device]
|
||||
if CONF_NAME not in config:
|
||||
config[CONF_NAME] = device
|
||||
if not setup_device(hass, config):
|
||||
_LOGGER.error("Couldn\'t set up %s", config[CONF_NAME])
|
||||
|
||||
# Services to communicate with device.
|
||||
descriptions = load_yaml_config_file(
|
||||
os.path.join(os.path.dirname(__file__), 'services.yaml'))
|
||||
|
||||
def vapix_service(call):
|
||||
"""Service to send a message."""
|
||||
for _, device in AXIS_DEVICES.items():
|
||||
if device.name == call.data[CONF_NAME]:
|
||||
response = device.do_request(call.data[SERVICE_CGI],
|
||||
call.data[SERVICE_ACTION],
|
||||
call.data[SERVICE_PARAM])
|
||||
hass.bus.async_fire(SERVICE_VAPIX_CALL_RESPONSE, response)
|
||||
return True
|
||||
_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,
|
||||
descriptions[DOMAIN][SERVICE_VAPIX_CALL],
|
||||
schema=SERVICE_SCHEMA)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def setup_device(hass, config):
|
||||
"""Set up device."""
|
||||
from axis import AxisDevice
|
||||
|
||||
config['hass'] = hass
|
||||
device = AxisDevice(config) # Initialize device
|
||||
enable_metadatastream = False
|
||||
|
||||
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", config[CONF_HOST])
|
||||
return False
|
||||
|
||||
for component in config[CONF_INCLUDE]:
|
||||
if component in EVENT_TYPES:
|
||||
# Sensors are created by device calling event_initialized
|
||||
# when receiving initialize messages on metadatastream
|
||||
device.add_event_topic(convert(component, 'type', 'subscribe'))
|
||||
if not enable_metadatastream:
|
||||
enable_metadatastream = True
|
||||
else:
|
||||
discovery.load_platform(hass, component, DOMAIN, config)
|
||||
|
||||
if enable_metadatastream:
|
||||
device.initialize_new_event = event_initialized
|
||||
if not device.initiate_metadatastream():
|
||||
hass.components.persistent_notification.create(
|
||||
'Dependency missing for sensors, '
|
||||
'please check documentation',
|
||||
title=DOMAIN,
|
||||
notification_id='axis_notification')
|
||||
|
||||
AXIS_DEVICES[device.serial_number] = device
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _read_config(hass):
|
||||
"""Read Axis config."""
|
||||
path = hass.config.path(CONFIG_FILE)
|
||||
|
||||
if not os.path.isfile(path):
|
||||
return {}
|
||||
|
||||
with open(path) as f_handle:
|
||||
# Guard against empty file
|
||||
return json.loads(f_handle.read() or '{}')
|
||||
|
||||
|
||||
def _write_config(hass, config):
|
||||
"""Write Axis config."""
|
||||
data = json.dumps(config)
|
||||
with open(hass.config.path(CONFIG_FILE), 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(data)
|
||||
|
||||
|
||||
def event_initialized(event):
|
||||
"""Register event initialized on metadatastream here."""
|
||||
hass = event.device_config('hass')
|
||||
discovery.load_platform(hass,
|
||||
convert(event.topic, 'topic', 'platform'),
|
||||
DOMAIN, {'axis_event': event})
|
||||
|
||||
|
||||
class AxisDeviceEvent(Entity):
|
||||
"""Representation of a Axis device event."""
|
||||
|
||||
def __init__(self, axis_event):
|
||||
"""Initialize the event."""
|
||||
self.axis_event = axis_event
|
||||
self._event_class = convert(self.axis_event.topic, 'topic', 'class')
|
||||
self._name = '{}_{}_{}'.format(self.axis_event.device_name,
|
||||
convert(self.axis_event.topic,
|
||||
'topic', 'type'),
|
||||
self.axis_event.id)
|
||||
self.axis_event.callback = self._update_callback
|
||||
|
||||
def _update_callback(self):
|
||||
"""Update the sensor's state, if needed."""
|
||||
self.update()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the event."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the event."""
|
||||
return self._event_class
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the event."""
|
||||
attr = {}
|
||||
|
||||
tripped = self.axis_event.is_tripped
|
||||
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
|
||||
|
||||
location = self.axis_event.device_config(ATTR_LOCATION)
|
||||
if location:
|
||||
attr[ATTR_LOCATION] = location
|
||||
|
||||
return attr
|
||||
|
||||
|
||||
def convert(item, from_key, to_key):
|
||||
"""Translate between Axis and HASS syntax."""
|
||||
for entry in REMAP:
|
||||
if entry[from_key] == item:
|
||||
return entry[to_key]
|
||||
|
||||
|
||||
REMAP = [{'type': 'motion',
|
||||
'class': 'motion',
|
||||
'topic': 'tns1:VideoAnalytics/tnsaxis:MotionDetection',
|
||||
'subscribe': 'onvif:VideoAnalytics/axis:MotionDetection',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'vmd3',
|
||||
'class': 'motion',
|
||||
'topic': 'tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1',
|
||||
'subscribe': 'onvif:RuleEngine/axis:VMD3/vmd3_video_1',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'pir',
|
||||
'class': 'motion',
|
||||
'topic': 'tns1:Device/tnsaxis:Sensor/PIR',
|
||||
'subscribe': 'onvif:Device/axis:Sensor/axis:PIR',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'sound',
|
||||
'class': 'sound',
|
||||
'topic': 'tns1:AudioSource/tnsaxis:TriggerLevel',
|
||||
'subscribe': 'onvif:AudioSource/axis:TriggerLevel',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'daynight',
|
||||
'class': 'light',
|
||||
'topic': 'tns1:VideoSource/tnsaxis:DayNightVision',
|
||||
'subscribe': 'onvif:VideoSource/axis:DayNightVision',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'tampering',
|
||||
'class': 'safety',
|
||||
'topic': 'tns1:VideoSource/tnsaxis:Tampering',
|
||||
'subscribe': 'onvif:VideoSource/axis:Tampering',
|
||||
'platform': 'binary_sensor'},
|
||||
{'type': 'input',
|
||||
'class': 'input',
|
||||
'topic': 'tns1:Device/tnsaxis:IO/Port',
|
||||
'subscribe': 'onvif:Device/axis:IO/Port',
|
||||
'platform': 'binary_sensor'}, ]
|
@@ -37,14 +37,14 @@ def setup(hass, config):
|
||||
# noqa: F821
|
||||
|
||||
def setup_output(pin):
|
||||
"""Setup a GPIO as output."""
|
||||
"""Set up a GPIO as output."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.setup(pin, GPIO.OUT)
|
||||
|
||||
|
||||
def setup_input(pin, pull_mode):
|
||||
"""Setup a GPIO as input."""
|
||||
"""Set up a GPIO as input."""
|
||||
# pylint: disable=import-error,undefined-variable
|
||||
import Adafruit_BBIO.GPIO as GPIO
|
||||
GPIO.setup(pin, GPIO.IN, # noqa: F821
|
||||
|
@@ -14,7 +14,6 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.const import (STATE_ON, STATE_OFF)
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
from homeassistant.helpers.deprecation import deprecated_substitute
|
||||
|
||||
DOMAIN = 'binary_sensor'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
@@ -57,7 +56,7 @@ class BinarySensorDevice(Entity):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return None
|
||||
|
||||
@property
|
||||
@@ -66,7 +65,6 @@ class BinarySensorDevice(Entity):
|
||||
return STATE_ON if self.is_on else STATE_OFF
|
||||
|
||||
@property
|
||||
@deprecated_substitute('sensor_class')
|
||||
def device_class(self):
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return None
|
||||
|
81
homeassistant/components/binary_sensor/abode.py
Normal file
81
homeassistant/components/binary_sensor/abode.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
This component provides HA binary_sensor support for Abode Security System.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.abode/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.abode import (CONF_ATTRIBUTION, DATA_ABODE)
|
||||
from homeassistant.const import (ATTR_ATTRIBUTION)
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
|
||||
DEPENDENCIES = ['abode']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Sensor types: Name, device_class
|
||||
SENSOR_TYPES = {
|
||||
'Door Contact': 'opening',
|
||||
'Motion Camera': 'motion',
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up a sensor for an Abode device."""
|
||||
data = hass.data.get(DATA_ABODE)
|
||||
|
||||
sensors = []
|
||||
for sensor in data.devices:
|
||||
_LOGGER.debug('Sensor type %s', sensor.type)
|
||||
if sensor.type in ['Door Contact', 'Motion Camera']:
|
||||
sensors.append(AbodeBinarySensor(hass, data, sensor))
|
||||
|
||||
_LOGGER.debug('Adding %d sensors', len(sensors))
|
||||
add_devices(sensors)
|
||||
|
||||
|
||||
class AbodeBinarySensor(BinarySensorDevice):
|
||||
"""A binary sensor implementation for Abode device."""
|
||||
|
||||
def __init__(self, hass, data, device):
|
||||
"""Initialize a sensor for Abode device."""
|
||||
super(AbodeBinarySensor, self).__init__()
|
||||
self._device = device
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return "{0} {1}".format(self._device.type, self._device.name)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
if self._device.type == 'Door Contact':
|
||||
return self._device.status != 'Closed'
|
||||
elif self._device.type == 'Motion Camera':
|
||||
return self._device.get_value('motion_event') == '1'
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the binary sensor."""
|
||||
return SENSOR_TYPES.get(self._device.type)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attrs = {}
|
||||
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
|
||||
attrs['device_id'] = self._device.device_id
|
||||
attrs['battery_low'] = self._device.battery_low
|
||||
|
||||
return attrs
|
||||
|
||||
def update(self):
|
||||
"""Update the device state."""
|
||||
self._device.refresh()
|
@@ -11,7 +11,6 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
from homeassistant.const import (STATE_ON, STATE_OFF, STATE_OPEN, STATE_CLOSED)
|
||||
from homeassistant.components.alarmdecoder import (ZONE_SCHEMA,
|
||||
CONF_ZONES,
|
||||
CONF_ZONE_NAME,
|
||||
@@ -27,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup AlarmDecoder binary sensor devices."""
|
||||
"""Set up the AlarmDecoder binary sensor devices."""
|
||||
configured_zones = discovery_info[CONF_ZONES]
|
||||
|
||||
devices = []
|
||||
@@ -36,10 +35,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
device_config_data = ZONE_SCHEMA(configured_zones[zone_num])
|
||||
zone_type = device_config_data[CONF_ZONE_TYPE]
|
||||
zone_name = device_config_data[CONF_ZONE_NAME]
|
||||
device = AlarmDecoderBinarySensor(hass,
|
||||
zone_num,
|
||||
zone_name,
|
||||
zone_type)
|
||||
device = AlarmDecoderBinarySensor(
|
||||
hass, zone_num, zone_name, zone_type)
|
||||
devices.append(device)
|
||||
|
||||
async_add_devices(devices)
|
||||
@@ -58,7 +55,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
|
||||
self._name = zone_name
|
||||
self._type = zone_type
|
||||
|
||||
_LOGGER.debug('AlarmDecoderBinarySensor: Setup up zone: ' + zone_name)
|
||||
_LOGGER.debug("Setup up zone: %s", self._name)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
@@ -69,14 +66,6 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_ZONE_RESTORE, self._restore_callback)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the binary sensor."""
|
||||
if self._type == 'opening':
|
||||
return STATE_OPEN if self.is_on else STATE_CLOSED
|
||||
|
||||
return STATE_ON if self.is_on else STATE_OFF
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the entity."""
|
||||
|
@@ -15,7 +15,7 @@ DEPENDENCIES = ['android_ip_webcam']
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup IP Webcam binary sensors."""
|
||||
"""Set up the IP Webcam binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
@@ -28,7 +28,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
|
||||
"""Represents an IP Webcam binary sensor."""
|
||||
"""Representation of an IP Webcam binary sensor."""
|
||||
|
||||
def __init__(self, name, host, ipcam, sensor):
|
||||
"""Initialize the binary sensor."""
|
||||
@@ -47,7 +47,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@@ -20,9 +20,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Setup an Online Status binary sensor."""
|
||||
add_entities((OnlineStatus(config, apcupsd.DATA),))
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up an APCUPSd Online Status binary sensor."""
|
||||
add_devices([OnlineStatus(config, apcupsd.DATA)], True)
|
||||
|
||||
|
||||
class OnlineStatus(BinarySensorDevice):
|
||||
@@ -33,7 +33,6 @@ class OnlineStatus(BinarySensorDevice):
|
||||
self._config = config
|
||||
self._data = data
|
||||
self._state = None
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@@ -13,10 +13,9 @@ import voluptuous as vol
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS)
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,7 +25,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_RESOURCE): cv.url,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_PIN): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -35,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the aREST binary sensor."""
|
||||
resource = config.get(CONF_RESOURCE)
|
||||
pin = config.get(CONF_PIN)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
try:
|
||||
response = requests.get(resource, timeout=10).json()
|
||||
@@ -51,7 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
add_devices([ArestBinarySensor(
|
||||
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
|
||||
device_class, pin)])
|
||||
device_class, pin)], True)
|
||||
|
||||
|
||||
class ArestBinarySensor(BinarySensorDevice):
|
||||
@@ -64,12 +62,11 @@ class ArestBinarySensor(BinarySensorDevice):
|
||||
self._name = name
|
||||
self._device_class = device_class
|
||||
self._pin = pin
|
||||
self.update()
|
||||
|
||||
if self._pin is not None:
|
||||
request = requests.get(
|
||||
'{}/mode/{}/i'.format(self._resource, self._pin), timeout=10)
|
||||
if request.status_code is not 200:
|
||||
if request.status_code != 200:
|
||||
_LOGGER.error("Can't set mode of %s", self._resource)
|
||||
|
||||
@property
|
||||
|
68
homeassistant/components/binary_sensor/axis.py
Normal file
68
homeassistant/components/binary_sensor/axis.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
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
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice)
|
||||
from homeassistant.components.axis import (AxisDeviceEvent)
|
||||
from homeassistant.const import (CONF_TRIGGER_TIME)
|
||||
from homeassistant.helpers.event import track_point_in_utc_time
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
DEPENDENCIES = ['axis']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Axis device event."""
|
||||
add_devices([AxisBinarySensor(discovery_info['axis_event'], hass)], True)
|
||||
|
||||
|
||||
class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
|
||||
"""Representation of a binary Axis event."""
|
||||
|
||||
def __init__(self, axis_event, hass):
|
||||
"""Initialize the binary sensor."""
|
||||
self.hass = hass
|
||||
self._state = False
|
||||
self._delay = axis_event.device_config(CONF_TRIGGER_TIME)
|
||||
self._timer = None
|
||||
AxisDeviceEvent.__init__(self, axis_event)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if event is active."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data and update the state."""
|
||||
self._state = self.axis_event.is_tripped
|
||||
|
||||
def _update_callback(self):
|
||||
"""Update the sensor's state, if needed."""
|
||||
self.update()
|
||||
|
||||
if self._timer is not None:
|
||||
self._timer()
|
||||
self._timer = None
|
||||
|
||||
if self._delay > 0 and not self.is_on:
|
||||
# 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.",
|
||||
self._name, self._delay)
|
||||
self.schedule_update_ha_state()
|
||||
self._timer = None
|
||||
|
||||
self._timer = track_point_in_utc_time(
|
||||
self.hass, _delay_update,
|
||||
utcnow() + timedelta(seconds=self._delay))
|
||||
else:
|
||||
self.schedule_update_ha_state()
|
@@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Beaglebone Black GPIO devices."""
|
||||
"""Set up the Beaglebone Black GPIO devices."""
|
||||
pins = config.get(CONF_PINS)
|
||||
|
||||
binary_sensors = []
|
||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class BBBGPIOBinarySensor(BinarySensorDevice):
|
||||
"""Represent a binary sensor that uses Beaglebone Black GPIO."""
|
||||
"""Representation of a binary sensor that uses Beaglebone Black GPIO."""
|
||||
|
||||
def __init__(self, pin, params):
|
||||
"""Initialize the Beaglebone Black binary sensor."""
|
||||
|
@@ -24,7 +24,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class BlinkCameraMotionSensor(BinarySensorDevice):
|
||||
"""A representation of a Blink binary sensor."""
|
||||
"""Representation of a Blink binary sensor."""
|
||||
|
||||
def __init__(self, name, data):
|
||||
"""Initialize the sensor."""
|
||||
|
@@ -18,7 +18,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['bloomsky']
|
||||
|
||||
# These are the available sensors mapped to binary_sensor class
|
||||
SENSOR_TYPES = {
|
||||
'Rain': 'moisture',
|
||||
'Night': None,
|
||||
@@ -31,18 +30,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the available BloomSky weather binary sensors."""
|
||||
"""Set up the available BloomSky weather binary sensors."""
|
||||
bloomsky = get_component('bloomsky')
|
||||
# Default needed in case of discovery
|
||||
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
|
||||
|
||||
for device in bloomsky.BLOOMSKY.devices.values():
|
||||
for variable in sensors:
|
||||
add_devices([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)])
|
||||
add_devices(
|
||||
[BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True)
|
||||
|
||||
|
||||
class BloomSkySensor(BinarySensorDevice):
|
||||
"""Represent a single binary sensor in a BloomSky device."""
|
||||
"""Representation of a single binary sensor in a BloomSky device."""
|
||||
|
||||
def __init__(self, bs, device, sensor_name):
|
||||
"""Initialize a BloomSky binary sensor."""
|
||||
@@ -51,11 +51,11 @@ class BloomSkySensor(BinarySensorDevice):
|
||||
self._sensor_name = sensor_name
|
||||
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
|
||||
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
|
||||
self.update()
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the BloomSky device and this sensor."""
|
||||
"""Return the name of the BloomSky device and this sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
|
@@ -4,19 +4,18 @@ Support for custom shell commands to retrieve values.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.command_line/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.sensor.command_line import CommandSensorData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_COMMAND, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
CONF_COMMAND, CONF_DEVICE_CLASS)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
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_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
})
|
||||
@@ -39,24 +37,24 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Command line Binary Sensor."""
|
||||
"""Set up the Command line Binary Sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
command = config.get(CONF_COMMAND)
|
||||
payload_off = config.get(CONF_PAYLOAD_OFF)
|
||||
payload_on = config.get(CONF_PAYLOAD_ON)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
data = CommandSensorData(command)
|
||||
data = CommandSensorData(hass, command)
|
||||
|
||||
add_devices([CommandBinarySensor(
|
||||
hass, data, name, device_class, payload_on, payload_off,
|
||||
value_template)])
|
||||
value_template)], True)
|
||||
|
||||
|
||||
class CommandBinarySensor(BinarySensorDevice):
|
||||
"""Represent a command line binary sensor."""
|
||||
"""Representation of a command line binary sensor."""
|
||||
|
||||
def __init__(self, hass, data, name, device_class, payload_on,
|
||||
payload_off, value_template):
|
||||
@@ -69,7 +67,6 @@ class CommandBinarySensor(BinarySensorDevice):
|
||||
self._payload_on = payload_on
|
||||
self._payload_off = payload_off
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@@ -67,17 +67,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
if zone['number'] not in exclude:
|
||||
sensors.append(
|
||||
Concord232ZoneSensor(
|
||||
hass, client, zone, zone_types.get(zone['number'],
|
||||
get_opening_type(zone)))
|
||||
hass, client, zone, zone_types.get(
|
||||
zone['number'], get_opening_type(zone))
|
||||
)
|
||||
)
|
||||
|
||||
add_devices(sensors)
|
||||
|
||||
return True
|
||||
add_devices(sensors, True)
|
||||
|
||||
|
||||
def get_opening_type(zone):
|
||||
"""Helper function to try to guess sensor type from name."""
|
||||
"""Return the result of the type guessing from name."""
|
||||
if 'MOTION' in zone['name']:
|
||||
return 'motion'
|
||||
if 'KEY' in zone['name']:
|
||||
@@ -99,7 +98,6 @@ class Concord232ZoneSensor(BinarySensorDevice):
|
||||
self._zone = zone
|
||||
self._number = zone['number']
|
||||
self._zone_type = zone_type
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@@ -123,13 +121,13 @@ class Concord232ZoneSensor(BinarySensorDevice):
|
||||
return bool(self._zone['state'] == 'Normal')
|
||||
|
||||
def update(self):
|
||||
""""Get updated stats from API."""
|
||||
"""Get updated stats from API."""
|
||||
last_update = datetime.datetime.now() - self._client.last_zone_update
|
||||
_LOGGER.debug("Zone: %s ", self._zone)
|
||||
if last_update > datetime.timedelta(seconds=1):
|
||||
self._client.zones = self._client.list_zones()
|
||||
self._client.last_zone_update = datetime.datetime.now()
|
||||
_LOGGER.debug("Updated from Zone: %s", self._zone['name'])
|
||||
_LOGGER.debug("Updated from zone: %s", self._zone['name'])
|
||||
|
||||
if hasattr(self._client, 'zones'):
|
||||
self._zone = next((x for x in self._client.zones
|
||||
|
@@ -8,7 +8,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Demo binary sensor platform."""
|
||||
"""Set up the Demo binary sensor platform."""
|
||||
add_devices([
|
||||
DemoBinarySensor('Basement Floor Wet', False, 'moisture'),
|
||||
DemoBinarySensor('Movement Backyard', True, 'motion'),
|
||||
@@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class DemoBinarySensor(BinarySensorDevice):
|
||||
"""A Demo binary sensor."""
|
||||
"""representation of a Demo binary sensor."""
|
||||
|
||||
def __init__(self, name, state, device_class):
|
||||
"""Initialize the demo sensor."""
|
||||
|
@@ -8,19 +8,18 @@ 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.digital_ocean import (
|
||||
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
|
||||
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
|
||||
ATTR_REGION, ATTR_VCPUS)
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
ATTR_REGION, ATTR_VCPUS, DATA_DIGITAL_OCEAN)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'Droplet'
|
||||
DEFAULT_SENSOR_CLASS = 'motion'
|
||||
DEFAULT_DEVICE_CLASS = 'moving'
|
||||
DEPENDENCIES = ['digital_ocean']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
@@ -30,19 +29,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Digital Ocean droplet sensor."""
|
||||
digital_ocean = get_component('digital_ocean')
|
||||
digital = hass.data.get(DATA_DIGITAL_OCEAN)
|
||||
if not digital:
|
||||
return False
|
||||
|
||||
droplets = config.get(CONF_DROPLETS)
|
||||
|
||||
dev = []
|
||||
for droplet in droplets:
|
||||
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
|
||||
droplet_id = digital.get_droplet_id(droplet)
|
||||
if droplet_id is None:
|
||||
_LOGGER.error("Droplet %s is not available", droplet)
|
||||
return False
|
||||
dev.append(DigitalOceanBinarySensor(
|
||||
digital_ocean.DIGITAL_OCEAN, droplet_id))
|
||||
dev.append(DigitalOceanBinarySensor(digital, droplet_id))
|
||||
|
||||
add_devices(dev)
|
||||
add_devices(dev, True)
|
||||
|
||||
|
||||
class DigitalOceanBinarySensor(BinarySensorDevice):
|
||||
@@ -53,7 +54,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
|
||||
self._digital_ocean = do
|
||||
self._droplet_id = droplet_id
|
||||
self._state = None
|
||||
self.update()
|
||||
self.data = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -68,7 +69,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEFAULT_SENSOR_CLASS
|
||||
return DEFAULT_DEVICE_CLASS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
@@ -13,7 +13,7 @@ ECOBEE_CONFIG_FILE = 'ecobee.conf'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Ecobee sensors."""
|
||||
"""Set up the Ecobee sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = ecobee.NETWORK
|
||||
@@ -26,7 +26,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
dev.append(EcobeeBinarySensor(sensor['name'], index))
|
||||
|
||||
add_devices(dev)
|
||||
add_devices(dev, True)
|
||||
|
||||
|
||||
class EcobeeBinarySensor(BinarySensorDevice):
|
||||
@@ -39,7 +39,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
|
||||
self.index = sensor_index
|
||||
self._state = None
|
||||
self._device_class = 'occupancy'
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
69
homeassistant/components/binary_sensor/eight_sleep.py
Normal file
69
homeassistant/components/binary_sensor/eight_sleep.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Support for Eight Sleep binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.eight_sleep/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.eight_sleep import (
|
||||
DATA_EIGHT, EightSleepHeatEntity, CONF_BINARY_SENSORS, NAME_MAP)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['eight_sleep']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up the eight sleep binary sensor."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
name = 'Eight'
|
||||
sensors = discovery_info[CONF_BINARY_SENSORS]
|
||||
eight = hass.data[DATA_EIGHT]
|
||||
|
||||
all_sensors = []
|
||||
|
||||
for sensor in sensors:
|
||||
all_sensors.append(EightHeatSensor(name, eight, sensor))
|
||||
|
||||
async_add_devices(all_sensors, True)
|
||||
|
||||
|
||||
class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
|
||||
"""Representation of a Eight Sleep heat-based sensor."""
|
||||
|
||||
def __init__(self, name, eight, sensor):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(eight)
|
||||
|
||||
self._sensor = sensor
|
||||
self._mapped_name = NAME_MAP.get(self._sensor, self._sensor)
|
||||
self._name = '{} {}'.format(name, self._mapped_name)
|
||||
self._state = None
|
||||
|
||||
self._side = self._sensor.split('_')[0]
|
||||
self._userid = self._eight.fetch_userid(self._side)
|
||||
self._usrobj = self._eight.users[self._userid]
|
||||
|
||||
_LOGGER.debug("Presence Sensor: %s, Side: %s, User: %s",
|
||||
self._sensor, self._side, self._userid)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor, if any."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
self._state = self._usrobj.bed_presence
|
@@ -12,9 +12,8 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.components import enocean
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_ID, CONF_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_NAME, CONF_ID, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,16 +23,15 @@ DEFAULT_NAME = 'EnOcean binary sensor'
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Binary Sensor platform fo EnOcean."""
|
||||
"""Set up the Binary Sensor platform for EnOcean."""
|
||||
dev_id = config.get(CONF_ID)
|
||||
devname = config.get(CONF_NAME)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
add_devices([EnOceanBinarySensor(dev_id, devname, device_class)])
|
||||
|
||||
@@ -44,7 +42,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
|
||||
def __init__(self, dev_id, devname, device_class):
|
||||
"""Initialize the EnOcean binary sensor."""
|
||||
enocean.EnOceanDevice.__init__(self)
|
||||
self.stype = "listener"
|
||||
self.stype = 'listener'
|
||||
self.dev_id = dev_id
|
||||
self.which = -1
|
||||
self.onoff = -1
|
||||
@@ -53,7 +51,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The default name for the binary sensor."""
|
||||
"""Return the default name for the binary sensor."""
|
||||
return self.devname
|
||||
|
||||
@property
|
||||
@@ -80,7 +78,13 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
|
||||
elif value2 == 0x10:
|
||||
self.which = 1
|
||||
self.onoff = 1
|
||||
self.hass.bus.fire('button_pressed', {"id": self.dev_id,
|
||||
elif value2 == 0x37:
|
||||
self.which = 10
|
||||
self.onoff = 0
|
||||
elif value2 == 0x15:
|
||||
self.which = 10
|
||||
self.onoff = 1
|
||||
self.hass.bus.fire('button_pressed', {'id': self.dev_id,
|
||||
'pushed': value,
|
||||
'which': self.which,
|
||||
'onoff': self.onoff})
|
||||
|
@@ -15,13 +15,14 @@ from homeassistant.components.envisalink import (
|
||||
SIGNAL_ZONE_UPDATE)
|
||||
from homeassistant.const import ATTR_LAST_TRIP_TIME
|
||||
|
||||
DEPENDENCIES = ['envisalink']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['envisalink']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup Envisalink binary sensor devices."""
|
||||
"""Set up the Envisalink binary sensor devices."""
|
||||
configured_zones = discovery_info['zones']
|
||||
|
||||
devices = []
|
||||
|
@@ -48,23 +48,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Create the binary sensor."""
|
||||
"""Set up the FFmpeg binary moition sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
# check source
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
# generate sensor object
|
||||
entity = FFmpegMotion(hass, manager, config)
|
||||
async_add_devices([entity])
|
||||
|
||||
|
||||
class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
|
||||
"""A binary sensor which use ffmpeg for noise detection."""
|
||||
"""A binary sensor which use FFmpeg for noise detection."""
|
||||
|
||||
def __init__(self, config):
|
||||
"""Constructor for binary sensor noise detection."""
|
||||
"""Init for the binary sensor noise detection."""
|
||||
super().__init__(config.get(CONF_INITIAL_STATE))
|
||||
|
||||
self._state = False
|
||||
@@ -79,7 +77,7 @@ class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
@@ -89,10 +87,10 @@ class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
|
||||
|
||||
|
||||
class FFmpegMotion(FFmpegBinarySensor):
|
||||
"""A binary sensor which use ffmpeg for noise detection."""
|
||||
"""A binary sensor which use FFmpeg for noise detection."""
|
||||
|
||||
def __init__(self, hass, manager, config):
|
||||
"""Initialize ffmpeg motion binary sensor."""
|
||||
"""Initialize FFmpeg motion binary sensor."""
|
||||
from haffmpeg import SensorMotion
|
||||
|
||||
super().__init__(config)
|
||||
@@ -125,4 +123,4 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
return "motion"
|
||||
return 'motion'
|
||||
|
@@ -45,23 +45,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Create the binary sensor."""
|
||||
"""Set up the FFmpeg noise binary sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
# check source
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
# generate sensor object
|
||||
entity = FFmpegNoise(hass, manager, config)
|
||||
async_add_devices([entity])
|
||||
|
||||
|
||||
class FFmpegNoise(FFmpegBinarySensor):
|
||||
"""A binary sensor which use ffmpeg for noise detection."""
|
||||
"""A binary sensor which use FFmpeg for noise detection."""
|
||||
|
||||
def __init__(self, hass, manager, config):
|
||||
"""Initialize ffmpeg noise binary sensor."""
|
||||
"""Initialize FFmpeg noise binary sensor."""
|
||||
from haffmpeg import SensorNoise
|
||||
|
||||
super().__init__(config)
|
||||
@@ -77,14 +75,12 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
if entity_ids is not None and self.entity_id not in entity_ids:
|
||||
return
|
||||
|
||||
# init config
|
||||
self.ffmpeg.set_options(
|
||||
time_duration=self._config.get(CONF_DURATION),
|
||||
time_reset=self._config.get(CONF_RESET),
|
||||
peak=self._config.get(CONF_PEAK),
|
||||
)
|
||||
|
||||
# run
|
||||
yield from self.ffmpeg.open_sensor(
|
||||
input_source=self._config.get(CONF_INPUT),
|
||||
output_dest=self._config.get(CONF_OUTPUT),
|
||||
@@ -94,4 +90,4 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
return "sound"
|
||||
return 'sound'
|
||||
|
@@ -110,7 +110,7 @@ def start_scanning(config, add_entities, client):
|
||||
|
||||
|
||||
def setup_button(hass, config, add_entities, client, address):
|
||||
"""Setup single button device."""
|
||||
"""Set up a single button device."""
|
||||
timeout = config.get(CONF_TIMEOUT)
|
||||
ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES)
|
||||
button = FlicButton(hass, client, address, timeout, ignored_click_types)
|
||||
@@ -199,11 +199,10 @@ class FlicButton(BinarySensorDevice):
|
||||
"Queued %s dropped for %s. Time in queue was %s",
|
||||
click_type, self.address, time_string)
|
||||
return True
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Queued %s allowed for %s. Time in queue was %s",
|
||||
click_type, self.address, time_string)
|
||||
return False
|
||||
_LOGGER.info(
|
||||
"Queued %s allowed for %s. Time in queue was %s",
|
||||
click_type, self.address, time_string)
|
||||
return False
|
||||
|
||||
def _on_up_down(self, channel, click_type, was_queued, time_diff):
|
||||
"""Update device state, if event was not queued."""
|
||||
|
@@ -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.2']
|
||||
REQUIREMENTS = ['pyhik==0.1.3']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_IGNORED = 'ignored'
|
||||
@@ -67,7 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Setup Hikvision binary sensor devices."""
|
||||
"""Set up the Hikvision binary sensor devices."""
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
port = config.get(CONF_PORT)
|
||||
@@ -77,16 +77,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
customize = config.get(CONF_CUSTOMIZE)
|
||||
|
||||
if config.get(CONF_SSL):
|
||||
protocol = "https"
|
||||
protocol = 'https'
|
||||
else:
|
||||
protocol = "http"
|
||||
protocol = 'http'
|
||||
|
||||
url = '{}://{}'.format(protocol, host)
|
||||
|
||||
data = HikvisionData(hass, url, port, name, username, password)
|
||||
|
||||
if data.sensors is None:
|
||||
_LOGGER.error('Hikvision event stream has no data, unable to setup.')
|
||||
_LOGGER.error("Hikvision event stream has no data, unable to setup")
|
||||
return False
|
||||
|
||||
entities = []
|
||||
@@ -104,7 +104,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
ignore = custom.get(CONF_IGNORED)
|
||||
delay = custom.get(CONF_DELAY)
|
||||
|
||||
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
|
||||
_LOGGER.debug("Entity: %s - %s, Options - Ignore: %s, Delay: %s",
|
||||
data.name, sensor_name, ignore, delay)
|
||||
if not ignore:
|
||||
entities.append(HikvisionBinarySensor(
|
||||
@@ -126,8 +126,8 @@ class HikvisionData(object):
|
||||
self._password = password
|
||||
|
||||
# Establish camera
|
||||
self.camdata = HikCamera(self._url, self._port,
|
||||
self._username, self._password)
|
||||
self.camdata = HikCamera(
|
||||
self._url, self._port, self._username, self._password)
|
||||
|
||||
if self._name is None:
|
||||
self._name = self.camdata.get_name
|
||||
@@ -251,7 +251,7 @@ class HikvisionBinarySensor(BinarySensorDevice):
|
||||
# Set timer to wait until updating the state
|
||||
def _delay_update(now):
|
||||
"""Timer callback for sensor update."""
|
||||
_LOGGER.debug('%s Called delayed (%ssec) update.',
|
||||
_LOGGER.debug("%s Called delayed (%ssec) update",
|
||||
self._name, self._delay)
|
||||
self.schedule_update_ha_state()
|
||||
self._timer = None
|
||||
|
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Support for Homematic binary sensors.
|
||||
Support for HomeMatic binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.homematic/
|
||||
@@ -14,28 +14,28 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['homematic']
|
||||
|
||||
SENSOR_TYPES_CLASS = {
|
||||
"Remote": None,
|
||||
"ShutterContact": "opening",
|
||||
"MaxShutterContact": "opening",
|
||||
"IPShutterContact": "opening",
|
||||
"Smoke": "smoke",
|
||||
"SmokeV2": "smoke",
|
||||
"Motion": "motion",
|
||||
"MotionV2": "motion",
|
||||
"RemoteMotion": None,
|
||||
"WeatherSensor": None,
|
||||
"TiltSensor": None,
|
||||
'Remote': None,
|
||||
'ShutterContact': 'opening',
|
||||
'MaxShutterContact': 'opening',
|
||||
'IPShutterContact': 'opening',
|
||||
'Smoke': 'smoke',
|
||||
'SmokeV2': 'smoke',
|
||||
'Motion': 'motion',
|
||||
'MotionV2': 'motion',
|
||||
'RemoteMotion': None,
|
||||
'WeatherSensor': None,
|
||||
'TiltSensor': None,
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Homematic binary sensor platform."""
|
||||
"""Set up the HomeMatic binary sensor platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
devices = []
|
||||
for config in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||
new_device = HMBinarySensor(hass, config)
|
||||
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||
new_device = HMBinarySensor(hass, conf)
|
||||
new_device.link_homematic()
|
||||
devices.append(new_device)
|
||||
|
||||
@@ -43,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
|
||||
class HMBinarySensor(HMDevice, BinarySensorDevice):
|
||||
"""Representation of a binary Homematic device."""
|
||||
"""Representation of a binary HomeMatic device."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -54,16 +54,14 @@ class HMBinarySensor(HMDevice, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
# If state is MOTION (RemoteMotion works only)
|
||||
if self._state == "MOTION":
|
||||
return "motion"
|
||||
"""Return the class of this sensor from DEVICE_CLASSES."""
|
||||
# If state is MOTION (Only RemoteMotion working)
|
||||
if self._state == 'MOTION':
|
||||
return 'motion'
|
||||
return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None)
|
||||
|
||||
def _init_data_struct(self):
|
||||
"""Generate a data struct (self._data) from the Homematic metadata."""
|
||||
# add state to data struct
|
||||
"""Generate the data dictionary (self._data) from metadata."""
|
||||
# Add state to data struct
|
||||
if self._state:
|
||||
_LOGGER.debug("%s init datastruct with main node '%s'", self._name,
|
||||
self._state)
|
||||
self._data.update({self._state: STATE_UNKNOWN})
|
||||
|
@@ -67,7 +67,7 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
|
||||
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)
|
||||
_LOGGER.info("Sensor state for %s is %s", self._address, sensorstate)
|
||||
return bool(sensorstate)
|
||||
|
||||
@property
|
||||
@@ -83,5 +83,5 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
|
||||
@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)
|
||||
_LOGGER.info("Received update calback from PLM for %s", self._address)
|
||||
self._hass.async_add_job(self.async_update_ha_state())
|
||||
|
@@ -64,7 +64,6 @@ class IssBinarySensor(BinarySensorDevice):
|
||||
self._state = None
|
||||
self._name = name
|
||||
self._show_on_map = show
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@@ -12,7 +12,6 @@ import homeassistant.components.isy994 as isy
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
VALUE_TO_STATE = {
|
||||
@@ -27,9 +26,9 @@ STATES = [STATE_OFF, STATE_ON, 'true', 'false']
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config: ConfigType,
|
||||
add_devices: Callable[[list], None], discovery_info=None):
|
||||
"""Setup the ISY994 binary sensor platform."""
|
||||
"""Set up the ISY994 binary sensor platform."""
|
||||
if isy.ISY is None or not isy.ISY.connected:
|
||||
_LOGGER.error('A connection has not been made to the ISY controller.')
|
||||
_LOGGER.error("A connection has not been made to the ISY controller")
|
||||
return False
|
||||
|
||||
devices = []
|
||||
|
@@ -11,7 +11,7 @@ DEPENDENCIES = ['knx']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the KNX binary sensor platform."""
|
||||
"""Set up the KNX binary sensor platform."""
|
||||
add_devices([KNXSwitch(hass, KNXConfig(config))])
|
||||
|
||||
|
||||
|
@@ -4,7 +4,6 @@ Support for MAX! Window Shutter via MAX! Cube.
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/maxcube/
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@@ -15,27 +14,24 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Iterate through all MAX! Devices and add window shutters to HASS."""
|
||||
"""Iterate through all MAX! Devices and add window shutters."""
|
||||
cube = hass.data[MAXCUBE_HANDLE].cube
|
||||
|
||||
# List of devices
|
||||
devices = []
|
||||
|
||||
for device in cube.devices:
|
||||
# Create device name by concatenating room name + device name
|
||||
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
|
||||
name = "{} {}".format(
|
||||
cube.room_by_id(device.room_id).name, device.name)
|
||||
|
||||
# Only add Window Shutters
|
||||
if cube.is_windowshutter(device):
|
||||
# add device to HASS
|
||||
devices.append(MaxCubeShutter(hass, name, device.rf_address))
|
||||
|
||||
if len(devices) > 0:
|
||||
if devices:
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
class MaxCubeShutter(BinarySensorDevice):
|
||||
"""MAX! Cube BinarySensor device."""
|
||||
"""Representation of a MAX! Cube Binary Sensor device."""
|
||||
|
||||
def __init__(self, hass, name, rf_address):
|
||||
"""Initialize MAX! Cube BinarySensorDevice."""
|
||||
@@ -47,7 +43,7 @@ class MaxCubeShutter(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling is required."""
|
||||
"""Return the polling state."""
|
||||
return True
|
||||
|
||||
@property
|
||||
@@ -68,9 +64,5 @@ class MaxCubeShutter(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Get latest data from MAX! Cube."""
|
||||
self._cubehandle.update()
|
||||
|
||||
# Get the device we want to update
|
||||
device = self._cubehandle.cube.device_by_rf(self._rf_address)
|
||||
|
||||
# Update our internal state
|
||||
self._state = device.is_open
|
||||
|
@@ -8,7 +8,7 @@ import logging
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.components.modbus as modbus
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.const import CONF_NAME, CONF_SLAVE
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
@@ -16,9 +16,8 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['modbus']
|
||||
|
||||
CONF_COIL = "coil"
|
||||
CONF_COILS = "coils"
|
||||
CONF_SLAVE = "slave"
|
||||
CONF_COIL = 'coil'
|
||||
CONF_COILS = 'coils'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COILS): [{
|
||||
@@ -30,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Modbus binary sensors."""
|
||||
"""Set up the Modbus binary sensors."""
|
||||
sensors = []
|
||||
for coil in config.get(CONF_COILS):
|
||||
sensors.append(ModbusCoilSensor(
|
||||
@@ -50,6 +49,11 @@ class ModbusCoilSensor(BinarySensorDevice):
|
||||
self._coil = int(coil)
|
||||
self._value = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the state of the sensor."""
|
||||
@@ -58,4 +62,10 @@ class ModbusCoilSensor(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Update the state of the sensor."""
|
||||
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
|
||||
self._value = result.bits[0]
|
||||
try:
|
||||
self._value = result.bits[0]
|
||||
except AttributeError:
|
||||
_LOGGER.error(
|
||||
'No response from modbus slave %s coil %s',
|
||||
self._slave,
|
||||
self._coil)
|
||||
|
@@ -15,10 +15,9 @@ 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_SENSOR_CLASS, CONF_DEVICE_CLASS)
|
||||
CONF_DEVICE_CLASS)
|
||||
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -31,7 +30,6 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
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_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -49,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
async_add_devices([MqttBinarySensor(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),
|
||||
config.get(CONF_DEVICE_CLASS),
|
||||
config.get(CONF_QOS),
|
||||
config.get(CONF_PAYLOAD_ON),
|
||||
config.get(CONF_PAYLOAD_OFF),
|
||||
@@ -79,7 +77,7 @@ class MqttBinarySensor(BinarySensorDevice):
|
||||
"""
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
"""A new MQTT message has been received."""
|
||||
"""Handle a new received MQTT message."""
|
||||
if self._template is not None:
|
||||
payload = self._template.async_render_with_possible_json_value(
|
||||
payload)
|
||||
@@ -95,7 +93,7 @@ class MqttBinarySensor(BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
"""Return the polling state."""
|
||||
return False
|
||||
|
||||
@property
|
||||
|
@@ -4,62 +4,27 @@ Support for MySensors binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mysensors/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components import mysensors
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES,
|
||||
from homeassistant.components.binary_sensor import (DEVICE_CLASSES, DOMAIN,
|
||||
BinarySensorDevice)
|
||||
from homeassistant.const import STATE_ON
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = []
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the mysensors platform for sensors."""
|
||||
# Only act if loaded via mysensors by discovery event.
|
||||
# Otherwise gateway is not setup.
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
|
||||
if not gateways:
|
||||
return
|
||||
|
||||
for gateway in gateways:
|
||||
# Define the S_TYPES and V_TYPES that the platform should handle as
|
||||
# states. Map them in a dict of lists.
|
||||
pres = gateway.const.Presentation
|
||||
set_req = gateway.const.SetReq
|
||||
map_sv_types = {
|
||||
pres.S_DOOR: [set_req.V_TRIPPED],
|
||||
pres.S_MOTION: [set_req.V_TRIPPED],
|
||||
pres.S_SMOKE: [set_req.V_TRIPPED],
|
||||
}
|
||||
if float(gateway.protocol_version) >= 1.5:
|
||||
map_sv_types.update({
|
||||
pres.S_SPRINKLER: [set_req.V_TRIPPED],
|
||||
pres.S_WATER_LEAK: [set_req.V_TRIPPED],
|
||||
pres.S_SOUND: [set_req.V_TRIPPED],
|
||||
pres.S_VIBRATION: [set_req.V_TRIPPED],
|
||||
pres.S_MOISTURE: [set_req.V_TRIPPED],
|
||||
})
|
||||
|
||||
devices = {}
|
||||
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
|
||||
map_sv_types, devices, MySensorsBinarySensor, add_devices))
|
||||
"""Setup the mysensors platform for binary sensors."""
|
||||
mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsBinarySensor,
|
||||
add_devices=add_devices)
|
||||
|
||||
|
||||
class MySensorsBinarySensor(
|
||||
mysensors.MySensorsDeviceEntity, BinarySensorDevice):
|
||||
mysensors.MySensorsEntity, BinarySensorDevice):
|
||||
"""Represent the value of a MySensors Binary Sensor child node."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
if self.value_type in self._values:
|
||||
return self._values[self.value_type] == STATE_ON
|
||||
return False
|
||||
return self._values.get(self.value_type) == STATE_ON
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
|
95
homeassistant/components/binary_sensor/mystrom.py
Normal file
95
homeassistant/components/binary_sensor/mystrom.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Support for the myStrom buttons.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mystrom/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (BinarySensorDevice, DOMAIN)
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Set up myStrom Binary Sensor."""
|
||||
hass.http.register_view(MyStromView(async_add_devices))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MyStromView(HomeAssistantView):
|
||||
"""View to handle requests from myStrom buttons."""
|
||||
|
||||
url = '/api/mystrom'
|
||||
name = 'api:mystrom'
|
||||
|
||||
def __init__(self, add_devices):
|
||||
"""Initialize the myStrom URL endpoint."""
|
||||
self.buttons = {}
|
||||
self.add_devices = add_devices
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request):
|
||||
"""The GET request received from a myStrom button."""
|
||||
res = yield from self._handle(request.app['hass'], request.query)
|
||||
return res
|
||||
|
||||
@asyncio.coroutine
|
||||
def _handle(self, hass, data):
|
||||
"""Handle requests to the myStrom endpoint."""
|
||||
button_action = list(data.keys())[0]
|
||||
button_id = data[button_action]
|
||||
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
|
||||
|
||||
if button_action not in ['single', 'double', 'long', 'touch']:
|
||||
_LOGGER.error(
|
||||
"Received unidentified message from myStrom button: %s", data)
|
||||
return ("Received unidentified message: {}".format(data),
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if entity_id not in self.buttons:
|
||||
_LOGGER.info("New myStrom button/action detected: %s/%s",
|
||||
button_id, button_action)
|
||||
self.buttons[entity_id] = MyStromBinarySensor(
|
||||
'{}_{}'.format(button_id, button_action))
|
||||
hass.async_add_job(self.add_devices, [self.buttons[entity_id]])
|
||||
else:
|
||||
new_state = True if self.buttons[entity_id].state == 'off' \
|
||||
else False
|
||||
self.buttons[entity_id].async_on_update(new_state)
|
||||
|
||||
|
||||
class MyStromBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a myStrom button."""
|
||||
|
||||
def __init__(self, button_id):
|
||||
"""Initialize the myStrom Binary sensor."""
|
||||
self._button_id = button_id
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._button_id
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def async_on_update(self, value):
|
||||
"""Receive an update."""
|
||||
self._state = value
|
||||
self.hass.async_add_job(self.async_update_ha_state())
|
@@ -16,15 +16,18 @@ DEPENDENCIES = ['nest']
|
||||
|
||||
BINARY_TYPES = ['online']
|
||||
|
||||
CLIMATE_BINARY_TYPES = ['fan',
|
||||
'is_using_emergency_heat',
|
||||
'is_locked',
|
||||
'has_leaf']
|
||||
CLIMATE_BINARY_TYPES = [
|
||||
'fan',
|
||||
'is_using_emergency_heat',
|
||||
'is_locked',
|
||||
'has_leaf',
|
||||
]
|
||||
|
||||
CAMERA_BINARY_TYPES = [
|
||||
'motion_detected',
|
||||
'sound_detected',
|
||||
'person_detected']
|
||||
'person_detected',
|
||||
]
|
||||
|
||||
_BINARY_TYPES_DEPRECATED = [
|
||||
'hvac_ac_state',
|
||||
@@ -34,7 +37,8 @@ _BINARY_TYPES_DEPRECATED = [
|
||||
'hvac_heat_x3_state',
|
||||
'hvac_alt_heat_state',
|
||||
'hvac_alt_heat_x2_state',
|
||||
'hvac_emer_heat_state']
|
||||
'hvac_emer_heat_state',
|
||||
]
|
||||
|
||||
_VALID_BINARY_SENSOR_TYPES = BINARY_TYPES + CLIMATE_BINARY_TYPES \
|
||||
+ CAMERA_BINARY_TYPES
|
||||
@@ -43,7 +47,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup Nest binary sensors."""
|
||||
"""Set up the Nest binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
@@ -93,7 +97,7 @@ class NestBinarySensor(NestSensor, BinarySensorDevice):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
|
@@ -7,6 +7,7 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.netatmo/.
|
||||
"""
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
@@ -16,10 +17,9 @@ from homeassistant.loader import get_component
|
||||
from homeassistant.const import CONF_TIMEOUT, CONF_OFFSET
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
DEPENDENCIES = ["netatmo"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['netatmo']
|
||||
|
||||
# These are the available sensors mapped to binary_sensor class
|
||||
WELCOME_SENSOR_TYPES = {
|
||||
@@ -34,8 +34,8 @@ PRESENCE_SENSOR_TYPES = {
|
||||
"Outdoor vehicle": "motion"
|
||||
}
|
||||
TAG_SENSOR_TYPES = {
|
||||
"Tag Vibration": 'vibration',
|
||||
"Tag Open": 'opening'
|
||||
"Tag Vibration": "vibration",
|
||||
"Tag Open": "opening"
|
||||
}
|
||||
|
||||
CONF_HOME = 'home'
|
||||
@@ -44,35 +44,36 @@ CONF_WELCOME_SENSORS = 'welcome_sensors'
|
||||
CONF_PRESENCE_SENSORS = 'presence_sensors'
|
||||
CONF_TAG_SENSORS = 'tag_sensors'
|
||||
|
||||
DEFAULT_TIMEOUT = 15
|
||||
DEFAULT_OFFSET = 90
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOME): cv.string,
|
||||
vol.Optional(CONF_TIMEOUT): cv.positive_int,
|
||||
vol.Optional(CONF_OFFSET): cv.positive_int,
|
||||
vol.Optional(CONF_CAMERAS, default=[]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(
|
||||
CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES.keys()):
|
||||
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
|
||||
vol.Optional(
|
||||
CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES.keys()):
|
||||
vol.Optional(CONF_HOME): cv.string,
|
||||
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int,
|
||||
vol.Optional(CONF_PRESENCE_SENSORS, default=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.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup access to Netatmo binary sensor."""
|
||||
"""Set up the access to Netatmo binary sensor."""
|
||||
netatmo = get_component('netatmo')
|
||||
home = config.get(CONF_HOME, None)
|
||||
timeout = config.get(CONF_TIMEOUT, 15)
|
||||
offset = config.get(CONF_OFFSET, 90)
|
||||
home = config.get(CONF_HOME)
|
||||
timeout = config.get(CONF_TIMEOUT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
|
||||
module_name = None
|
||||
|
||||
import lnetatmo
|
||||
try:
|
||||
data = CameraData(netatmo.NETATMO_AUTH, home)
|
||||
if data.get_camera_names() == []:
|
||||
if not data.get_camera_names():
|
||||
return None
|
||||
except lnetatmo.NoDevice:
|
||||
return None
|
||||
@@ -85,35 +86,31 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
|
||||
for camera_name in data.get_camera_names():
|
||||
camera_type = data.get_camera_type(camera=camera_name, home=home)
|
||||
if camera_type == "NACamera":
|
||||
if camera_type == 'NACamera':
|
||||
if CONF_CAMERAS in config:
|
||||
if config[CONF_CAMERAS] != [] and \
|
||||
camera_name not in config[CONF_CAMERAS]:
|
||||
continue
|
||||
for variable in welcome_sensors:
|
||||
add_devices([NetatmoBinarySensor(data, camera_name,
|
||||
module_name, home, timeout,
|
||||
offset, camera_type,
|
||||
variable)])
|
||||
if camera_type == "NOC":
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout,
|
||||
offset, camera_type, variable)], True)
|
||||
if camera_type == 'NOC':
|
||||
if CONF_CAMERAS in config:
|
||||
if config[CONF_CAMERAS] != [] and \
|
||||
camera_name not in config[CONF_CAMERAS]:
|
||||
continue
|
||||
for variable in presence_sensors:
|
||||
add_devices([NetatmoBinarySensor(data, camera_name,
|
||||
module_name, home, timeout,
|
||||
offset, camera_type,
|
||||
variable)])
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout, offset,
|
||||
camera_type, variable)], True)
|
||||
|
||||
for module_name in data.get_module_names(camera_name):
|
||||
for variable in tag_sensors:
|
||||
camera_type = None
|
||||
add_devices([NetatmoBinarySensor(data, camera_name,
|
||||
module_name, home,
|
||||
timeout, offset,
|
||||
camera_type,
|
||||
variable)])
|
||||
add_devices([NetatmoBinarySensor(
|
||||
data, camera_name, module_name, home, timeout, offset,
|
||||
camera_type, variable)], True)
|
||||
|
||||
|
||||
class NetatmoBinarySensor(BinarySensorDevice):
|
||||
@@ -121,7 +118,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
|
||||
def __init__(self, data, camera_name, module_name, home,
|
||||
timeout, offset, camera_type, sensor):
|
||||
"""Setup for access to the Netatmo camera events."""
|
||||
"""Set up for access to the Netatmo camera events."""
|
||||
self._data = data
|
||||
self._camera_name = camera_name
|
||||
self._module_name = module_name
|
||||
@@ -129,23 +126,23 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
self._timeout = timeout
|
||||
self._offset = offset
|
||||
if home:
|
||||
self._name = home + ' / ' + camera_name
|
||||
self._name = '{} / {}'.format(home, camera_name)
|
||||
else:
|
||||
self._name = camera_name
|
||||
if module_name:
|
||||
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)
|
||||
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.update()
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The name of the Netatmo device and this sensor."""
|
||||
"""Return the name of the Netatmo device and this sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
@@ -156,12 +153,11 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor, from DEVICE_CLASSES."""
|
||||
if self._cameratype == "NACamera":
|
||||
if self._cameratype == 'NACamera':
|
||||
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
|
||||
elif self._cameratype == "NOC":
|
||||
elif self._cameratype == 'NOC':
|
||||
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
|
||||
else:
|
||||
return TAG_SENSOR_TYPES.get(self._sensor_name)
|
||||
return TAG_SENSOR_TYPES.get(self._sensor_name)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -173,51 +169,44 @@ class NetatmoBinarySensor(BinarySensorDevice):
|
||||
self._data.update()
|
||||
self._data.update_event()
|
||||
|
||||
if self._cameratype == "NACamera":
|
||||
if self._cameratype == 'NACamera':
|
||||
if self._sensor_name == "Someone known":
|
||||
self._state =\
|
||||
self._data.camera_data.someoneKnownSeen(self._home,
|
||||
self._camera_name,
|
||||
self._timeout*60)
|
||||
self._data.camera_data.someoneKnownSeen(
|
||||
self._home, self._camera_name, self._timeout*60)
|
||||
elif self._sensor_name == "Someone unknown":
|
||||
self._state =\
|
||||
self._data.camera_data.someoneUnknownSeen(
|
||||
self._home, self._camera_name, self._timeout*60)
|
||||
elif self._sensor_name == "Motion":
|
||||
self._state =\
|
||||
self._data.camera_data.motionDetected(self._home,
|
||||
self._camera_name,
|
||||
self._timeout*60)
|
||||
elif self._cameratype == "NOC":
|
||||
self._data.camera_data.motionDetected(
|
||||
self._home, self._camera_name, self._timeout*60)
|
||||
elif self._cameratype == 'NOC':
|
||||
if self._sensor_name == "Outdoor motion":
|
||||
self._state =\
|
||||
self._data.camera_data.outdoormotionDetected(
|
||||
self._home, self._camera_name, self._offset)
|
||||
elif self._sensor_name == "Outdoor human":
|
||||
self._state =\
|
||||
self._data.camera_data.humanDetected(self._home,
|
||||
self._camera_name,
|
||||
self._offset)
|
||||
self._data.camera_data.humanDetected(
|
||||
self._home, self._camera_name, self._offset)
|
||||
elif self._sensor_name == "Outdoor animal":
|
||||
self._state =\
|
||||
self._data.camera_data.animalDetected(self._home,
|
||||
self._camera_name,
|
||||
self._offset)
|
||||
self._data.camera_data.animalDetected(
|
||||
self._home, self._camera_name, self._offset)
|
||||
elif self._sensor_name == "Outdoor vehicle":
|
||||
self._state =\
|
||||
self._data.camera_data.carDetected(self._home,
|
||||
self._camera_name,
|
||||
self._offset)
|
||||
self._data.camera_data.carDetected(
|
||||
self._home, self._camera_name, self._offset)
|
||||
if self._sensor_name == "Tag Vibration":
|
||||
self._state =\
|
||||
self._data.camera_data.moduleMotionDetected(self._home,
|
||||
self._module_name,
|
||||
self._camera_name,
|
||||
self._timeout*60)
|
||||
self._data.camera_data.moduleMotionDetected(
|
||||
self._home, self._module_name, self._camera_name,
|
||||
self._timeout*60)
|
||||
elif self._sensor_name == "Tag Open":
|
||||
self._state =\
|
||||
self._data.camera_data.moduleOpened(self._home,
|
||||
self._module_name,
|
||||
self._camera_name)
|
||||
self._data.camera_data.moduleOpened(
|
||||
self._home, self._module_name, self._camera_name)
|
||||
else:
|
||||
return None
|
||||
|
@@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the NX584 binary sensor platform."""
|
||||
"""Set up the NX584 binary sensor platform."""
|
||||
from nx584 import client as nx584_client
|
||||
|
||||
host = config.get(CONF_HOST)
|
||||
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
client = nx584_client.Client('http://{}:{}'.format(host, port))
|
||||
zones = client.list_zones()
|
||||
except requests.exceptions.ConnectionError as ex:
|
||||
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
|
||||
_LOGGER.error("Unable to connect to NX584: %s", str(ex))
|
||||
return False
|
||||
|
||||
version = [int(v) for v in client.get_version().split('.')]
|
||||
|
@@ -9,18 +9,15 @@ import logging
|
||||
import requests
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, STATE_ON, STATE_OFF, CONF_MONITORED_CONDITIONS)
|
||||
from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['octoprint']
|
||||
|
||||
DOMAIN = "octoprint"
|
||||
DEFAULT_NAME = 'OctoPrint'
|
||||
|
||||
SENSOR_TYPES = {
|
||||
@@ -38,24 +35,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the available OctoPrint binary sensors."""
|
||||
octoprint = get_component('octoprint')
|
||||
"""Set up the available OctoPrint binary sensors."""
|
||||
octoprint_api = hass.data[DOMAIN]["api"]
|
||||
name = config.get(CONF_NAME)
|
||||
monitored_conditions = config.get(CONF_MONITORED_CONDITIONS,
|
||||
SENSOR_TYPES.keys())
|
||||
monitored_conditions = config.get(
|
||||
CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys())
|
||||
|
||||
devices = []
|
||||
for octo_type in monitored_conditions:
|
||||
new_sensor = OctoPrintBinarySensor(octoprint.OCTOPRINT,
|
||||
octo_type,
|
||||
SENSOR_TYPES[octo_type][2],
|
||||
name,
|
||||
SENSOR_TYPES[octo_type][3],
|
||||
SENSOR_TYPES[octo_type][0],
|
||||
SENSOR_TYPES[octo_type][1],
|
||||
'flags')
|
||||
new_sensor = OctoPrintBinarySensor(
|
||||
octoprint_api, octo_type, SENSOR_TYPES[octo_type][2],
|
||||
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
|
||||
SENSOR_TYPES[octo_type][1], 'flags')
|
||||
devices.append(new_sensor)
|
||||
add_devices(devices)
|
||||
add_devices(devices, True)
|
||||
|
||||
|
||||
class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
@@ -76,8 +69,6 @@ class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
self.api_endpoint = endpoint
|
||||
self.api_group = group
|
||||
self.api_tool = tool
|
||||
# Set initial state
|
||||
self.update()
|
||||
_LOGGER.debug("Created OctoPrint binary sensor %r", self)
|
||||
|
||||
@property
|
||||
@@ -85,18 +76,10 @@ class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self.is_on
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if binary sensor is on."""
|
||||
if self._state:
|
||||
return STATE_ON
|
||||
else:
|
||||
return STATE_OFF
|
||||
return bool(self._state)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
@@ -106,13 +89,9 @@ class OctoPrintBinarySensor(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Update state of sensor."""
|
||||
try:
|
||||
self._state = self.api.update(self.sensor_type,
|
||||
self.api_endpoint,
|
||||
self.api_group,
|
||||
self.api_tool)
|
||||
self._state = self.api.update(
|
||||
self.sensor_type, self.api_endpoint, self.api_group,
|
||||
self.api_tool)
|
||||
except requests.exceptions.ConnectionError:
|
||||
# Error calling the api, already logged in api.update()
|
||||
return
|
||||
|
||||
if self._state is None:
|
||||
_LOGGER.warning("Unable to locate value for %s", self.sensor_type)
|
||||
|
189
homeassistant/components/binary_sensor/pilight.py
Normal file
189
homeassistant/components/binary_sensor/pilight.py
Normal file
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
Support for Pilight binary sensors.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.pilight/
|
||||
"""
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import pilight
|
||||
from homeassistant.components.binary_sensor import (
|
||||
PLATFORM_SCHEMA,
|
||||
BinarySensorDevice,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DISARM_AFTER_TRIGGER,
|
||||
CONF_NAME,
|
||||
CONF_PAYLOAD,
|
||||
CONF_PAYLOAD_OFF,
|
||||
CONF_PAYLOAD_ON
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import track_point_in_time
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_VARIABLE = 'variable'
|
||||
CONF_RESET_DELAY_SEC = 'reset_delay_sec'
|
||||
|
||||
DEFAULT_NAME = 'Pilight Binary Sensor'
|
||||
DEPENDENCIES = ['pilight']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_VARIABLE): cv.string,
|
||||
vol.Required(CONF_PAYLOAD): vol.Schema(dict),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string,
|
||||
vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean,
|
||||
vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up Pilight Binary Sensor."""
|
||||
disarm = config.get(CONF_DISARM_AFTER_TRIGGER)
|
||||
if disarm:
|
||||
add_devices([PilightTriggerSensor(
|
||||
hass=hass,
|
||||
name=config.get(CONF_NAME),
|
||||
variable=config.get(CONF_VARIABLE),
|
||||
payload=config.get(CONF_PAYLOAD),
|
||||
on_value=config.get(CONF_PAYLOAD_ON),
|
||||
off_value=config.get(CONF_PAYLOAD_OFF),
|
||||
rst_dly_sec=config.get(CONF_RESET_DELAY_SEC),
|
||||
)])
|
||||
else:
|
||||
add_devices([PilightBinarySensor(
|
||||
hass=hass,
|
||||
name=config.get(CONF_NAME),
|
||||
variable=config.get(CONF_VARIABLE),
|
||||
payload=config.get(CONF_PAYLOAD),
|
||||
on_value=config.get(CONF_PAYLOAD_ON),
|
||||
off_value=config.get(CONF_PAYLOAD_OFF),
|
||||
)])
|
||||
|
||||
|
||||
class PilightBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a binary sensor that can be updated using Pilight."""
|
||||
|
||||
def __init__(self, hass, name, variable, payload, on_value, off_value):
|
||||
"""Initialize the sensor."""
|
||||
self._state = False
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._variable = variable
|
||||
self._payload = payload
|
||||
self._on_value = on_value
|
||||
self._off_value = off_value
|
||||
|
||||
hass.bus.listen(pilight.EVENT, self._handle_code)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def _handle_code(self, call):
|
||||
"""Handle received code by the pilight-daemon.
|
||||
|
||||
If the code matches the defined playload
|
||||
of this sensor the sensor state is changed accordingly.
|
||||
"""
|
||||
# Check if received code matches defined playoad
|
||||
# True if payload is contained in received code dict
|
||||
payload_ok = True
|
||||
for key in self._payload:
|
||||
if key not in call.data:
|
||||
payload_ok = False
|
||||
continue
|
||||
if self._payload[key] != call.data[key]:
|
||||
payload_ok = False
|
||||
# Read out variable if payload ok
|
||||
if payload_ok:
|
||||
if self._variable not in call.data:
|
||||
return
|
||||
value = call.data[self._variable]
|
||||
self._state = (value == self._on_value)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
|
||||
class PilightTriggerSensor(BinarySensorDevice):
|
||||
"""Representation of a binary sensor that can be updated using Pilight."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass,
|
||||
name,
|
||||
variable,
|
||||
payload,
|
||||
on_value,
|
||||
off_value,
|
||||
rst_dly_sec=30):
|
||||
"""Initialize the sensor."""
|
||||
self._state = False
|
||||
self._hass = hass
|
||||
self._name = name
|
||||
self._variable = variable
|
||||
self._payload = payload
|
||||
self._on_value = on_value
|
||||
self._off_value = off_value
|
||||
self._reset_delay_sec = rst_dly_sec
|
||||
self._delay_after = None
|
||||
self._hass = hass
|
||||
|
||||
hass.bus.listen(pilight.EVENT, self._handle_code)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return True if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
def _reset_state(self, call):
|
||||
self._state = False
|
||||
self._delay_after = None
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def _handle_code(self, call):
|
||||
"""Handle received code by the pilight-daemon.
|
||||
|
||||
If the code matches the defined playload
|
||||
of this sensor the sensor state is changed accordingly.
|
||||
"""
|
||||
# Check if received code matches defined playoad
|
||||
# True if payload is contained in received code dict
|
||||
payload_ok = True
|
||||
for key in self._payload:
|
||||
if key not in call.data:
|
||||
payload_ok = False
|
||||
continue
|
||||
if self._payload[key] != call.data[key]:
|
||||
payload_ok = False
|
||||
# Read out variable if payload ok
|
||||
if payload_ok:
|
||||
if self._variable not in call.data:
|
||||
return
|
||||
value = call.data[self._variable]
|
||||
self._state = (value == self._on_value)
|
||||
if self._delay_after is None:
|
||||
self._delay_after = dt_util.utcnow() + datetime.timedelta(
|
||||
seconds=self._reset_delay_sec)
|
||||
track_point_in_time(
|
||||
self._hass, self._reset_state,
|
||||
self._delay_after)
|
||||
self.schedule_update_ha_state()
|
@@ -28,13 +28,19 @@ CONF_PING_COUNT = 'count'
|
||||
|
||||
DEFAULT_NAME = 'Ping Binary sensor'
|
||||
DEFAULT_PING_COUNT = 5
|
||||
DEFAULT_SENSOR_CLASS = 'connectivity'
|
||||
DEFAULT_DEVICE_CLASS = 'connectivity'
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
PING_MATCHER = re.compile(
|
||||
r'(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)\/(?P<mdev>\d+.\d+)')
|
||||
|
||||
PING_MATCHER_BUSYBOX = re.compile(
|
||||
r'(?P<min>\d+.\d+)\/(?P<avg>\d+.\d+)\/(?P<max>\d+.\d+)')
|
||||
|
||||
WIN32_PING_MATCHER = re.compile(
|
||||
r'(?P<min>\d+)ms.+(?P<max>\d+)ms.+(?P<avg>\d+)ms')
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
@@ -67,7 +73,7 @@ class PingBinarySensor(BinarySensorDevice):
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return DEFAULT_SENSOR_CLASS
|
||||
return DEFAULT_DEVICE_CLASS
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -102,7 +108,7 @@ class PingData(object):
|
||||
|
||||
if sys.platform == 'win32':
|
||||
self._ping_cmd = [
|
||||
'ping', '-n', str(self._count), '-w 1000', self._ip_address]
|
||||
'ping', '-n', str(self._count), '-w', '1000', self._ip_address]
|
||||
else:
|
||||
self._ping_cmd = [
|
||||
'ping', '-n', '-q', '-c', str(self._count), '-W1',
|
||||
@@ -114,6 +120,23 @@ class PingData(object):
|
||||
self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
out = pinger.communicate()
|
||||
_LOGGER.debug("Output is %s", str(out))
|
||||
if sys.platform == 'win32':
|
||||
match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1])
|
||||
rtt_min, rtt_avg, rtt_max = match.groups()
|
||||
return {
|
||||
'min': rtt_min,
|
||||
'avg': rtt_avg,
|
||||
'max': rtt_max,
|
||||
'mdev': ''}
|
||||
if 'max/' not in str(out):
|
||||
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
|
||||
rtt_min, rtt_avg, rtt_max = match.groups()
|
||||
return {
|
||||
'min': rtt_min,
|
||||
'avg': rtt_avg,
|
||||
'max': rtt_max,
|
||||
'mdev': ''}
|
||||
match = PING_MATCHER.search(str(out).split('\n')[-1])
|
||||
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
|
||||
return {
|
||||
|
131
homeassistant/components/binary_sensor/raspihats.py
Normal file
131
homeassistant/components/binary_sensor/raspihats.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Configure a binary_sensor using a digital input from a raspihats board.
|
||||
|
||||
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
|
||||
)
|
||||
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
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['raspihats']
|
||||
|
||||
DEFAULT_INVERT_LOGIC = False
|
||||
DEFAULT_DEVICE_CLASS = None
|
||||
|
||||
_CHANNELS_SCHEMA = vol.Schema([{
|
||||
vol.Required(CONF_INDEX): cv.positive_int,
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
|
||||
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): cv.string,
|
||||
}])
|
||||
|
||||
_I2C_HATS_SCHEMA = vol.Schema([{
|
||||
vol.Required(CONF_BOARD): vol.In(I2C_HAT_NAMES),
|
||||
vol.Required(CONF_ADDRESS): vol.Coerce(int),
|
||||
vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA
|
||||
}])
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_I2C_HATS): _I2C_HATS_SCHEMA,
|
||||
})
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the raspihats binary_sensor devices."""
|
||||
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]
|
||||
binary_sensors = []
|
||||
i2c_hat_configs = config.get(CONF_I2C_HATS)
|
||||
for i2c_hat_config in i2c_hat_configs:
|
||||
address = i2c_hat_config[CONF_ADDRESS]
|
||||
board = i2c_hat_config[CONF_BOARD]
|
||||
try:
|
||||
I2CHatBinarySensor.I2C_HATS_MANAGER.register_board(board, address)
|
||||
for channel_config in i2c_hat_config[CONF_CHANNELS]:
|
||||
binary_sensors.append(
|
||||
I2CHatBinarySensor(
|
||||
address,
|
||||
channel_config[CONF_INDEX],
|
||||
channel_config[CONF_NAME],
|
||||
channel_config[CONF_INVERT_LOGIC],
|
||||
channel_config[CONF_DEVICE_CLASS]
|
||||
)
|
||||
)
|
||||
except I2CHatsException as ex:
|
||||
_LOGGER.error(
|
||||
"Failed to register " + board + "I2CHat@" + hex(address) + " "
|
||||
+ str(ex)
|
||||
)
|
||||
add_devices(binary_sensors)
|
||||
|
||||
|
||||
class I2CHatBinarySensor(BinarySensorDevice):
|
||||
"""Represents 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."""
|
||||
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
|
||||
)
|
||||
|
||||
def online_callback():
|
||||
"""Callback fired when board is online."""
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_online_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
online_callback
|
||||
)
|
||||
|
||||
def edge_callback(state):
|
||||
"""Read digital input state."""
|
||||
self._state = state
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
self.I2C_HATS_MANAGER.register_di_callback(
|
||||
self._address,
|
||||
self._channel,
|
||||
edge_callback
|
||||
)
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of this sensor."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of this sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""Polling not needed for this sensor."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the state of this sensor."""
|
||||
return self._state != self._invert_logic
|
@@ -14,11 +14,10 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.components.sensor.rest import RestData
|
||||
from homeassistant.const import (
|
||||
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
|
||||
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
|
||||
HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||
@@ -44,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the REST binary sensor."""
|
||||
"""Set up the REST binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
resource = config.get(CONF_RESOURCE)
|
||||
method = config.get(CONF_METHOD)
|
||||
@@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
headers = config.get(CONF_HEADERS)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
@@ -74,7 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
return False
|
||||
|
||||
add_devices([RestBinarySensor(
|
||||
hass, rest, name, device_class, value_template)])
|
||||
hass, rest, name, device_class, value_template)], True)
|
||||
|
||||
|
||||
class RestBinarySensor(BinarySensorDevice):
|
||||
@@ -89,7 +87,6 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
self._state = False
|
||||
self._previous_data = None
|
||||
self._value_template = value_template
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -107,6 +104,8 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
if self.rest.data is None:
|
||||
return False
|
||||
|
||||
response = self.rest.data
|
||||
|
||||
if self._value_template is not None:
|
||||
response = self._value_template.\
|
||||
async_render_with_possible_json_value(self.rest.data, False)
|
||||
@@ -114,8 +113,8 @@ class RestBinarySensor(BinarySensorDevice):
|
||||
try:
|
||||
return bool(int(response))
|
||||
except ValueError:
|
||||
return {"true": True, "on": True, "open": True,
|
||||
"yes": True}.get(response.lower(), False)
|
||||
return {'true': True, 'on': True, 'open': True,
|
||||
'yes': True}.get(response.lower(), False)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from REST API and updates the state."""
|
||||
|
232
homeassistant/components/binary_sensor/rfxtrx.py
Normal file
232
homeassistant/components/binary_sensor/rfxtrx.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Support for RFXtrx binary sensors.
|
||||
|
||||
Lighting4 devices (sensors based on PT2262 encoder) are supported and
|
||||
tested. Other types may need some work.
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
from homeassistant.components import rfxtrx
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import event as evt
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.rfxtrx import (
|
||||
ATTR_AUTOMATIC_ADD, ATTR_NAME, ATTR_OFF_DELAY, ATTR_FIREEVENT,
|
||||
ATTR_DATA_BITS, CONF_DEVICES
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS, CONF_COMMAND_ON, CONF_COMMAND_OFF
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["rfxtrx"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = vol.Schema({
|
||||
vol.Required("platform"): rfxtrx.DOMAIN,
|
||||
vol.Optional(CONF_DEVICES, default={}): vol.All(
|
||||
dict, rfxtrx.valid_binary_sensor),
|
||||
vol.Optional(ATTR_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."""
|
||||
import RFXtrx as rfxtrxmod
|
||||
sensors = []
|
||||
|
||||
for packet_id, entity in config['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[ATTR_DATA_BITS] is not None:
|
||||
_LOGGER.info("Masked device id: %s",
|
||||
rfxtrx.get_pt2262_deviceid(device_id,
|
||||
entity[ATTR_DATA_BITS]))
|
||||
|
||||
_LOGGER.info("Add %s rfxtrx.binary_sensor (class %s)",
|
||||
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
|
||||
|
||||
device = RfxtrxBinarySensor(event, entity[ATTR_NAME],
|
||||
entity[CONF_DEVICE_CLASS],
|
||||
entity[ATTR_FIREEVENT],
|
||||
entity[ATTR_OFF_DELAY],
|
||||
entity[ATTR_DATA_BITS],
|
||||
entity[CONF_COMMAND_ON],
|
||||
entity[CONF_COMMAND_OFF])
|
||||
device.hass = hass
|
||||
device.is_lighting4 = (packet_id[2:4] == '13')
|
||||
sensors.append(device)
|
||||
rfxtrx.RFX_DEVICES[device_id] = device
|
||||
|
||||
add_devices_callback(sensors)
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def binary_sensor_update(event):
|
||||
"""Callback for control updates from the RFXtrx gateway."""
|
||||
if not isinstance(event, rfxtrxmod.ControlEvent):
|
||||
return
|
||||
|
||||
device_id = slugify(event.device.id_string.lower())
|
||||
|
||||
if device_id in rfxtrx.RFX_DEVICES:
|
||||
sensor = rfxtrx.RFX_DEVICES[device_id]
|
||||
else:
|
||||
sensor = rfxtrx.get_pt2262_device(device_id)
|
||||
|
||||
if sensor is None:
|
||||
# Add the entity if not exists and automatic_add is True
|
||||
if not config[ATTR_AUTOMATIC_ADD]:
|
||||
return
|
||||
|
||||
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.info("Found possible matching deviceid %s.",
|
||||
poss_id)
|
||||
|
||||
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
|
||||
sensor = RfxtrxBinarySensor(event, pkt_id)
|
||||
sensor.hass = hass
|
||||
sensor.is_lighting4 = (pkt_id[2:4] == '13')
|
||||
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)
|
||||
|
||||
elif not isinstance(sensor, RfxtrxBinarySensor):
|
||||
return
|
||||
else:
|
||||
_LOGGER.info("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:
|
||||
cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits)
|
||||
sensor.apply_cmd(int(cmd, 16))
|
||||
else:
|
||||
sensor.update_state(True)
|
||||
else:
|
||||
rfxtrx.apply_received_command(event)
|
||||
|
||||
if (sensor.is_on and sensor.off_delay is not None and
|
||||
sensor.delay_listener is None):
|
||||
|
||||
def off_delay_listener(now):
|
||||
"""Switch device off after a delay."""
|
||||
sensor.delay_listener = None
|
||||
sensor.update_state(False)
|
||||
|
||||
sensor.delay_listener = evt.track_point_in_time(
|
||||
hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay
|
||||
)
|
||||
|
||||
# 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."""
|
||||
|
||||
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."""
|
||||
self.event = event
|
||||
self._name = name
|
||||
self._should_fire_event = should_fire
|
||||
self._device_class = device_class
|
||||
self._off_delay = off_delay
|
||||
self._state = False
|
||||
self.is_lighting4 = False
|
||||
self.delay_listener = None
|
||||
self._data_bits = data_bits
|
||||
self._cmd_on = cmd_on
|
||||
self._cmd_off = cmd_off
|
||||
|
||||
if data_bits is not None:
|
||||
self._masked_id = rfxtrx.get_pt2262_deviceid(
|
||||
event.device.id_string.lower(),
|
||||
data_bits)
|
||||
|
||||
def __str__(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the device name."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def masked_id(self):
|
||||
"""Return the masked device id (isolated address bits)."""
|
||||
return self._masked_id
|
||||
|
||||
@property
|
||||
def data_bits(self):
|
||||
"""Return the number of data bits."""
|
||||
return self._data_bits
|
||||
|
||||
@property
|
||||
def cmd_on(self):
|
||||
"""Return the value of the 'On' command."""
|
||||
return self._cmd_on
|
||||
|
||||
@property
|
||||
def cmd_off(self):
|
||||
"""Return the value of the 'Off' command."""
|
||||
return self._cmd_off
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def should_fire_event(self):
|
||||
"""Return is the device must fire event."""
|
||||
return self._should_fire_event
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the sensor class."""
|
||||
return self._device_class
|
||||
|
||||
@property
|
||||
def off_delay(self):
|
||||
"""Return the off_delay attribute value."""
|
||||
return self._off_delay
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if the sensor state is True."""
|
||||
return self._state
|
||||
|
||||
def apply_cmd(self, cmd):
|
||||
"""Apply a command for updating the state."""
|
||||
if cmd == self.cmd_on:
|
||||
self.update_state(True)
|
||||
elif cmd == self.cmd_off:
|
||||
self.update_state(False)
|
||||
|
||||
def update_state(self, state):
|
||||
"""Update the state of the device."""
|
||||
self._state = state
|
||||
self.schedule_update_ha_state()
|
@@ -41,10 +41,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the Raspberry PI GPIO devices."""
|
||||
pull_mode = config.get('pull_mode', DEFAULT_PULL_MODE)
|
||||
bouncetime = config.get('bouncetime', DEFAULT_BOUNCETIME)
|
||||
invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC)
|
||||
"""Set up the Raspberry PI GPIO devices."""
|
||||
pull_mode = config.get(CONF_PULL_MODE)
|
||||
bouncetime = config.get(CONF_BOUNCETIME)
|
||||
invert_logic = config.get(CONF_INVERT_LOGIC)
|
||||
|
||||
binary_sensors = []
|
||||
ports = config.get('ports')
|
||||
|
93
homeassistant/components/binary_sensor/rpi_pfio.py
Normal file
93
homeassistant/components/binary_sensor/rpi_pfio.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Support for binary sensor using the PiFace Digital I/O module on a RPi.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.rpi_pfio/
|
||||
"""
|
||||
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
|
||||
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_PORTS = 'ports'
|
||||
|
||||
DEFAULT_INVERT_LOGIC = False
|
||||
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):
|
||||
cv.positive_int,
|
||||
vol.Optional(ATTR_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
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the PiFace Digital Input devices."""
|
||||
binary_sensors = []
|
||||
ports = config.get('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]
|
||||
|
||||
binary_sensors.append(RPiPFIOBinarySensor(
|
||||
hass, port, name, settle_time, invert_logic))
|
||||
add_devices(binary_sensors, True)
|
||||
|
||||
rpi_pfio.activate_listener(hass)
|
||||
|
||||
|
||||
class RPiPFIOBinarySensor(BinarySensorDevice):
|
||||
"""Represent a binary sensor that a PiFace Digital Input."""
|
||||
|
||||
def __init__(self, hass, port, name, settle_time, invert_logic):
|
||||
"""Initialize the RPi binary sensor."""
|
||||
self._port = port
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._invert_logic = invert_logic
|
||||
self._state = None
|
||||
|
||||
def read_pfio(port):
|
||||
"""Read state from PFIO."""
|
||||
self._state = rpi_pfio.read_input(self._port)
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
rpi_pfio.edge_detect(hass, self._port, read_pfio, settle_time)
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the state of the entity."""
|
||||
return self._state != self._invert_logic
|
||||
|
||||
def update(self):
|
||||
"""Update the PFIO state."""
|
||||
self._state = rpi_pfio.read_input(self._port)
|
@@ -11,7 +11,7 @@ DEPENDENCIES = ['sleepiq']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the SleepIQ sensors."""
|
||||
"""Set up the SleepIQ sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
|
99
homeassistant/components/binary_sensor/spc.py
Normal file
99
homeassistant/components/binary_sensor/spc.py
Normal file
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPC_TYPE_TO_DEVICE_CLASS = {'0': 'motion',
|
||||
'1': 'opening',
|
||||
'3': 'smoke'}
|
||||
|
||||
|
||||
SPC_INPUT_TO_SENSOR_STATE = {'0': STATE_OFF,
|
||||
'1': STATE_ON}
|
||||
|
||||
|
||||
def _get_device_class(spc_type):
|
||||
return SPC_TYPE_TO_DEVICE_CLASS.get(spc_type, None)
|
||||
|
||||
|
||||
def _get_sensor_state(spc_input):
|
||||
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])
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
return
|
||||
|
||||
async_add_entities(
|
||||
_create_sensor(hass, zone)
|
||||
for zone in discovery_info[ATTR_DISCOVER_DEVICES]
|
||||
if _get_device_class(zone['type']))
|
||||
|
||||
|
||||
class SpcBinarySensor(BinarySensorDevice):
|
||||
"""Represents a sensor based on an SPC zone."""
|
||||
|
||||
def __init__(self, zone_id, name, state, device_class, spc_registry):
|
||||
"""Initialize the sensor device."""
|
||||
self._zone_id = zone_id
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._device_class = device_class
|
||||
|
||||
spc_registry.register_sensor_device(zone_id, self)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update_from_spc(self, state):
|
||||
"""Update the state of the device."""
|
||||
self._state = state
|
||||
yield from self.async_update_ha_state()
|
||||
|
||||
@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) -> bool:
|
||||
"""Whether the device is hidden by default."""
|
||||
# these type of sensors are probably mainly used for automations
|
||||
return True
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""The device class."""
|
||||
return self._device_class
|
86
homeassistant/components/binary_sensor/tapsaff.py
Normal file
86
homeassistant/components/binary_sensor/tapsaff.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
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 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)
|
||||
|
||||
REQUIREMENTS = ['tapsaff==0.1.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_LOCATION = 'location'
|
||||
|
||||
DEFAULT_NAME = 'Taps Aff'
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_LOCATION): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Taps Aff binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
location = config.get(CONF_LOCATION)
|
||||
|
||||
taps_aff_data = TapsAffData(location)
|
||||
|
||||
add_devices([TapsAffSensor(taps_aff_data, name)], True)
|
||||
|
||||
|
||||
class TapsAffSensor(BinarySensorDevice):
|
||||
"""Implementation of a Taps Aff binary sensor."""
|
||||
|
||||
def __init__(self, taps_aff_data, name):
|
||||
"""Initialize the Taps Aff sensor."""
|
||||
self.data = taps_aff_data
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return '{}'.format(self._name)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if taps aff."""
|
||||
return self.data.is_taps_aff
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
self.data.update()
|
||||
|
||||
|
||||
class TapsAffData(object):
|
||||
"""Class for handling the data retrieval for pins."""
|
||||
|
||||
def __init__(self, location):
|
||||
"""Initialize the sensor."""
|
||||
from tapsaff import TapsAff
|
||||
|
||||
self._is_taps_aff = None
|
||||
self.taps_aff = TapsAff(location)
|
||||
|
||||
@property
|
||||
def is_taps_aff(self):
|
||||
"""Return true if taps aff."""
|
||||
return self._is_taps_aff
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from the Taps Aff API and updates the states."""
|
||||
try:
|
||||
self._is_taps_aff = self.taps_aff.is_taps_aff
|
||||
except RuntimeError:
|
||||
_LOGGER.error("Update failed. Check configured location")
|
@@ -27,5 +27,5 @@ class TcpBinarySensor(BinarySensorDevice, TcpSensor):
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""True if the binary sensor is on."""
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state == self._config[CONF_VALUE_ON]
|
||||
|
@@ -15,11 +15,9 @@ from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
|
||||
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS,
|
||||
EVENT_HOMEASSISTANT_START, STATE_ON)
|
||||
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.helpers.restore_state import async_get_last_state
|
||||
@@ -30,7 +28,6 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -41,7 +38,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
"""Setup template binary sensors."""
|
||||
"""Set up template binary sensors."""
|
||||
sensors = []
|
||||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
@@ -49,23 +46,18 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
|
||||
value_template.extract_entities())
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
device_class = get_deprecated(
|
||||
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
|
||||
sensors.append(
|
||||
BinarySensorTemplate(
|
||||
hass,
|
||||
device,
|
||||
friendly_name,
|
||||
device_class,
|
||||
value_template,
|
||||
hass, device, friendly_name, device_class, value_template,
|
||||
entity_ids)
|
||||
)
|
||||
if not sensors:
|
||||
_LOGGER.error('No sensors added')
|
||||
_LOGGER.error("No sensors added")
|
||||
return False
|
||||
|
||||
async_add_devices(sensors, True)
|
||||
@@ -79,8 +71,8 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
value_template, entity_ids):
|
||||
"""Initialize the Template binary sensor."""
|
||||
self.hass = hass
|
||||
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
|
||||
hass=hass)
|
||||
self.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, device, hass=hass)
|
||||
self._name = friendly_name
|
||||
self._device_class = device_class
|
||||
self._template = value_template
|
||||
@@ -96,7 +88,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
|
||||
@callback
|
||||
def template_bsensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
"""Handle the target device state changes."""
|
||||
self.hass.async_add_job(self.async_update_ha_state(True))
|
||||
|
||||
@callback
|
||||
@@ -139,8 +131,8 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
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 template %s,'
|
||||
' the state is unknown.', self._name)
|
||||
_LOGGER.warning("Could not render template %s, "
|
||||
"the state is unknown", self._name)
|
||||
return
|
||||
_LOGGER.error('Could not render template %s: %s', self._name, ex)
|
||||
_LOGGER.error("Could not render template %s: %s", self._name, ex)
|
||||
self._state = False
|
||||
|
@@ -13,10 +13,9 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN, CONF_SENSOR_CLASS,
|
||||
CONF_NAME, CONF_ENTITY_ID, CONF_TYPE, STATE_UNKNOWN,
|
||||
ATTR_ENTITY_ID, CONF_DEVICE_CLASS)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -38,7 +37,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_THRESHOLD): vol.Coerce(float),
|
||||
vol.Required(CONF_TYPE): vol.In(SENSOR_TYPES),
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -50,7 +48,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
name = config.get(CONF_NAME)
|
||||
threshold = config.get(CONF_THRESHOLD)
|
||||
limit_type = config.get(CONF_TYPE)
|
||||
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = config.get(CONF_DEVICE_CLASS)
|
||||
|
||||
async_add_devices(
|
||||
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
|
||||
@@ -77,7 +75,7 @@ class ThresholdSensor(BinarySensorDevice):
|
||||
# pylint: disable=invalid-name
|
||||
def async_threshold_sensor_state_listener(
|
||||
entity, old_state, new_state):
|
||||
"""Called when the sensor changes state."""
|
||||
"""Handle sensor state changes."""
|
||||
if new_state.state == STATE_UNKNOWN:
|
||||
return
|
||||
|
||||
|
@@ -6,23 +6,17 @@ https://home-assistant.io/components/sensor.trend/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice,
|
||||
ENTITY_ID_FORMAT,
|
||||
PLATFORM_SCHEMA,
|
||||
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
|
||||
DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME,
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_SENSOR_CLASS,
|
||||
CONF_DEVICE_CLASS,
|
||||
STATE_UNKNOWN,)
|
||||
from homeassistant.helpers.deprecation import get_deprecated
|
||||
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, STATE_UNKNOWN)
|
||||
from homeassistant.helpers.entity import generate_entity_id
|
||||
from homeassistant.helpers.event import track_state_change
|
||||
|
||||
@@ -36,7 +30,6 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(CONF_INVERT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SENSOR_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||
})
|
||||
|
||||
@@ -47,26 +40,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Setup the trend sensors."""
|
||||
"""Set up the trend sensors."""
|
||||
sensors = []
|
||||
|
||||
for device, device_config in config[CONF_SENSORS].items():
|
||||
entity_id = device_config[ATTR_ENTITY_ID]
|
||||
attribute = device_config.get(CONF_ATTRIBUTE)
|
||||
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
|
||||
device_class = get_deprecated(
|
||||
device_config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
|
||||
device_class = device_config.get(CONF_DEVICE_CLASS)
|
||||
invert = device_config[CONF_INVERT]
|
||||
|
||||
sensors.append(
|
||||
SensorTrend(
|
||||
hass,
|
||||
device,
|
||||
friendly_name,
|
||||
entity_id,
|
||||
attribute,
|
||||
device_class,
|
||||
invert)
|
||||
hass, device, friendly_name, entity_id, attribute,
|
||||
device_class, invert)
|
||||
)
|
||||
if not sensors:
|
||||
_LOGGER.error("No sensors added")
|
||||
@@ -82,8 +69,8 @@ class SensorTrend(BinarySensorDevice):
|
||||
target_entity, attribute, device_class, invert):
|
||||
"""Initialize the sensor."""
|
||||
self._hass = hass
|
||||
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id,
|
||||
hass=hass)
|
||||
self.entity_id = generate_entity_id(
|
||||
ENTITY_ID_FORMAT, device_id, hass=hass)
|
||||
self._name = friendly_name
|
||||
self._target_entity = target_entity
|
||||
self._attribute = attribute
|
||||
@@ -95,7 +82,7 @@ class SensorTrend(BinarySensorDevice):
|
||||
|
||||
@callback
|
||||
def trend_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Called when the target device changes state."""
|
||||
"""Handle the target device state changes."""
|
||||
self.from_state = old_state
|
||||
self.to_state = new_state
|
||||
hass.async_add_job(self.async_update_ha_state(True))
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user