mirror of
https://github.com/home-assistant/core.git
synced 2025-09-23 11:59:37 +00:00
Compare commits
508 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a309a00929 | ||
![]() |
55be5bf880 | ||
![]() |
7b37dcd8ed | ||
![]() |
e36bdd717a | ||
![]() |
fa650b648c | ||
![]() |
ac2310e7f9 | ||
![]() |
aee5c16803 | ||
![]() |
5f0816ea25 | ||
![]() |
6a6037790f | ||
![]() |
d2b0c35319 | ||
![]() |
d707a1b072 | ||
![]() |
ca12db9271 | ||
![]() |
346a4b399d | ||
![]() |
2090252936 | ||
![]() |
a28091e94a | ||
![]() |
ae8cb0ccdf | ||
![]() |
06a608e342 | ||
![]() |
9af95e8577 | ||
![]() |
29a9781bf7 | ||
![]() |
877eddf43d | ||
![]() |
88e3e73bb4 | ||
![]() |
3aa1bcbb77 | ||
![]() |
f973b35cef | ||
![]() |
4e08aa8b05 | ||
![]() |
8e917ccf73 | ||
![]() |
0b62011626 | ||
![]() |
d520a02b8c | ||
![]() |
1e469b39ad | ||
![]() |
c2f615839d | ||
![]() |
657bf33e32 | ||
![]() |
0ca87007fd | ||
![]() |
d0d9d853f2 | ||
![]() |
8348878e7e | ||
![]() |
b70be5f2f2 | ||
![]() |
fddb565e4c | ||
![]() |
f3e6820042 | ||
![]() |
ae98f13181 | ||
![]() |
ab38e7d98a | ||
![]() |
9797b09d44 | ||
![]() |
4908d4358c | ||
![]() |
67d728fc50 | ||
![]() |
912409ed0c | ||
![]() |
ac8c889b0f | ||
![]() |
67a721d39b | ||
![]() |
d196fd136d | ||
![]() |
4f78674a4c | ||
![]() |
a7aca10668 | ||
![]() |
03b1c6ddee | ||
![]() |
661f1b69f2 | ||
![]() |
ccb34083fe | ||
![]() |
7f6b3c1130 | ||
![]() |
f2c3f76b8e | ||
![]() |
b6a3bcf87f | ||
![]() |
65423bb62b | ||
![]() |
104665d849 | ||
![]() |
fb1ba86b08 | ||
![]() |
cee72724b6 | ||
![]() |
a3d74651a8 | ||
![]() |
d88275d6d2 | ||
![]() |
42998f898b | ||
![]() |
875671cc2b | ||
![]() |
3b84b6e6d5 | ||
![]() |
3b1fb2f416 | ||
![]() |
226a0bcaad | ||
![]() |
57dd45318d | ||
![]() |
e666485ea9 | ||
![]() |
b5c8b5b91f | ||
![]() |
706607f1d2 | ||
![]() |
0788bbd629 | ||
![]() |
1b622925a1 | ||
![]() |
86c4fa0fc5 | ||
![]() |
e365dc398b | ||
![]() |
dfd29e6d73 | ||
![]() |
6780bded7e | ||
![]() |
56686dd14c | ||
![]() |
7268bcd9be | ||
![]() |
4f78e04315 | ||
![]() |
7bdac8ef2e | ||
![]() |
a66f4ca4ec | ||
![]() |
3345d85dca | ||
![]() |
9ad776e55d | ||
![]() |
2f2a908573 | ||
![]() |
9f76a8c12d | ||
![]() |
fe6ca522e8 | ||
![]() |
c46d0e4a49 | ||
![]() |
8db426e5da | ||
![]() |
bfacd9a1c3 | ||
![]() |
943c7ee11a | ||
![]() |
71155f548f | ||
![]() |
180bcad477 | ||
![]() |
01dc81d8fb | ||
![]() |
cea5cac6e2 | ||
![]() |
bb666b9ac6 | ||
![]() |
c9a9bd16fe | ||
![]() |
3e8728ad5f | ||
![]() |
d28d1ff657 | ||
![]() |
44c7743351 | ||
![]() |
f7ddbc7e1e | ||
![]() |
5216dc0ae1 | ||
![]() |
440c837eb6 | ||
![]() |
ffe8b94d75 | ||
![]() |
c56530a712 | ||
![]() |
50c32d57f5 | ||
![]() |
015e779d56 | ||
![]() |
01d0e70f44 | ||
![]() |
4c4e5f3fa9 | ||
![]() |
7127492767 | ||
![]() |
365578d053 | ||
![]() |
647d137daa | ||
![]() |
4248893007 | ||
![]() |
99109d162b | ||
![]() |
16336bf902 | ||
![]() |
69b19d54e8 | ||
![]() |
61acf944c0 | ||
![]() |
836b077bcc | ||
![]() |
1b5c9e922d | ||
![]() |
048f9e7daa | ||
![]() |
ae147fd9c7 | ||
![]() |
b9a9a92145 | ||
![]() |
ba0aaeeddb | ||
![]() |
bbe0f75336 | ||
![]() |
765882fc4d | ||
![]() |
908ae23738 | ||
![]() |
21cff003f8 | ||
![]() |
2c1da182e2 | ||
![]() |
39bdb562d3 | ||
![]() |
1d1f8df509 | ||
![]() |
ccb8b6b9c8 | ||
![]() |
c0dcd9c674 | ||
![]() |
6062854666 | ||
![]() |
a3c55b5e96 | ||
![]() |
89fc430eec | ||
![]() |
b2bb9cf134 | ||
![]() |
16d7f84be7 | ||
![]() |
0a25e86fab | ||
![]() |
c11a462f51 | ||
![]() |
a8758ed3a1 | ||
![]() |
2e802c88f8 | ||
![]() |
8c52e2c923 | ||
![]() |
ac9c9377c2 | ||
![]() |
11eee43fc7 | ||
![]() |
aed15761de | ||
![]() |
324dfe07b4 | ||
![]() |
f3c07a5653 | ||
![]() |
f4561891ae | ||
![]() |
c2b03332a0 | ||
![]() |
3a680bf7b7 | ||
![]() |
8a46d93be4 | ||
![]() |
87b770be08 | ||
![]() |
9a3c58213b | ||
![]() |
d4615fd432 | ||
![]() |
3318e65948 | ||
![]() |
09512e9f8b | ||
![]() |
96a0aa3d2b | ||
![]() |
4bb9f1800d | ||
![]() |
743833e5f3 | ||
![]() |
988b400a9c | ||
![]() |
19faf06ce7 | ||
![]() |
22b5690607 | ||
![]() |
e1d6964589 | ||
![]() |
fa4f27f78e | ||
![]() |
c0c5c33b20 | ||
![]() |
a6e0ab2b3a | ||
![]() |
f7f6c47973 | ||
![]() |
df86668dfc | ||
![]() |
5ace7de171 | ||
![]() |
0dd0b2fa03 | ||
![]() |
963b5db763 | ||
![]() |
f54c50fae3 | ||
![]() |
8a878bbe72 | ||
![]() |
4fbc404c47 | ||
![]() |
b5118c41a6 | ||
![]() |
17215709e1 | ||
![]() |
a0787cd9be | ||
![]() |
7e781946fa | ||
![]() |
732457745f | ||
![]() |
1f510389b8 | ||
![]() |
ae2e6f9d2a | ||
![]() |
e52542c4d7 | ||
![]() |
dd91b51435 | ||
![]() |
28a5fca7f4 | ||
![]() |
8f2567f30d | ||
![]() |
e1cc2acdf9 | ||
![]() |
ac945242dc | ||
![]() |
2879081772 | ||
![]() |
0d667c1bd9 | ||
![]() |
3b75fdccfd | ||
![]() |
5f6158f656 | ||
![]() |
e02042b33e | ||
![]() |
19254eee30 | ||
![]() |
a579fcf248 | ||
![]() |
873bf887a5 | ||
![]() |
b5022f5bcb | ||
![]() |
ae0ea0f088 | ||
![]() |
4717d072c9 | ||
![]() |
8aea538662 | ||
![]() |
7c51318861 | ||
![]() |
d885853b35 | ||
![]() |
6a21afa2a8 | ||
![]() |
da7c5518f3 | ||
![]() |
007d934214 | ||
![]() |
85ba4692a9 | ||
![]() |
521cc7247d | ||
![]() |
d216c1f2ac | ||
![]() |
1615a5ee81 | ||
![]() |
81f99efda1 | ||
![]() |
81810dd920 | ||
![]() |
56cf4e54a9 | ||
![]() |
9a4aad1777 | ||
![]() |
daff87fe5d | ||
![]() |
bfe1e8fef3 | ||
![]() |
b848c97211 | ||
![]() |
2316f7ace4 | ||
![]() |
c4ed2ecb61 | ||
![]() |
eac1f029e5 | ||
![]() |
1d3647e6a1 | ||
![]() |
2d3b117cb8 | ||
![]() |
7b5b909f0a | ||
![]() |
3ca97a0517 | ||
![]() |
e416f17e4d | ||
![]() |
76fec90fec | ||
![]() |
15a5cebd5f | ||
![]() |
9a8017aadd | ||
![]() |
d1beb92c5d | ||
![]() |
2abdfc9da6 | ||
![]() |
dd7d8d4792 | ||
![]() |
0e436ac80e | ||
![]() |
c7d983fd44 | ||
![]() |
0c0d4c460f | ||
![]() |
9aaab41985 | ||
![]() |
0763dc6089 | ||
![]() |
e9978e77bd | ||
![]() |
104350265d | ||
![]() |
20333703c5 | ||
![]() |
7f91501a36 | ||
![]() |
b27c46750c | ||
![]() |
f62322cfb4 | ||
![]() |
4f619691df | ||
![]() |
6a6bf517fe | ||
![]() |
7678d66464 | ||
![]() |
af76a336af | ||
![]() |
d666b15689 | ||
![]() |
4be9766498 | ||
![]() |
4080d6a822 | ||
![]() |
6d06844318 | ||
![]() |
a150d6dcf3 | ||
![]() |
a0390783bb | ||
![]() |
91b10e875f | ||
![]() |
f04969cf30 | ||
![]() |
cdde5a37cd | ||
![]() |
f0c7a7c1bf | ||
![]() |
7e3e4c1668 | ||
![]() |
02c8cd07f3 | ||
![]() |
4cf86262af | ||
![]() |
db7d0eb9b9 | ||
![]() |
fed23030d6 | ||
![]() |
8f6651af3d | ||
![]() |
896df9267a | ||
![]() |
cfa61a6b74 | ||
![]() |
1119da7e8a | ||
![]() |
59b4e42f8b | ||
![]() |
2f8381b1bf | ||
![]() |
5bbbe60635 | ||
![]() |
558da56d75 | ||
![]() |
a0403a8864 | ||
![]() |
bfaad97318 | ||
![]() |
c62961f40c | ||
![]() |
d6c15d2f45 | ||
![]() |
815502044e | ||
![]() |
08f5b49dc4 | ||
![]() |
fab55b0ea2 | ||
![]() |
649ec2fc8e | ||
![]() |
aacdc1bc2d | ||
![]() |
fcbea47c74 | ||
![]() |
ee7ce47860 | ||
![]() |
a25b94cd2d | ||
![]() |
21e0df42ac | ||
![]() |
66d70195c9 | ||
![]() |
f7f9126610 | ||
![]() |
52809396d4 | ||
![]() |
121d967732 | ||
![]() |
1603f7ac21 | ||
![]() |
2ba514253c | ||
![]() |
87abd193ee | ||
![]() |
049897365c | ||
![]() |
df3f7687d4 | ||
![]() |
eb90cefd84 | ||
![]() |
7ca4665711 | ||
![]() |
924c313c8a | ||
![]() |
da959c8f7b | ||
![]() |
c361358c6d | ||
![]() |
e13d5bdc10 | ||
![]() |
fc98faa425 | ||
![]() |
9da5bc9dce | ||
![]() |
f53c94ed2a | ||
![]() |
0431b983d2 | ||
![]() |
a9a523b05c | ||
![]() |
7a7fdc5de6 | ||
![]() |
e9206b26ad | ||
![]() |
f0b5e132af | ||
![]() |
c6e85cac0b | ||
![]() |
8e3492d4f5 | ||
![]() |
3ab04118f6 | ||
![]() |
c4ac3155e4 | ||
![]() |
be14b94705 | ||
![]() |
e9a7b66df6 | ||
![]() |
bf33144c2b | ||
![]() |
3ea29635a0 | ||
![]() |
2d4ee01c1a | ||
![]() |
5fbaaf41dc | ||
![]() |
0670b4f457 | ||
![]() |
4a95eee40f | ||
![]() |
deda2f86e7 | ||
![]() |
ede39454a2 | ||
![]() |
3b147bcbf7 | ||
![]() |
f1a0ca7cd3 | ||
![]() |
ca81c6e684 | ||
![]() |
c7f128f286 | ||
![]() |
e62ba49979 | ||
![]() |
6ea20090a4 | ||
![]() |
c43b7d10d8 | ||
![]() |
430fa24acd | ||
![]() |
601f2c693d | ||
![]() |
f17462b159 | ||
![]() |
3dc1ece33c | ||
![]() |
6852ccd8de | ||
![]() |
157f972d72 | ||
![]() |
7714160f4c | ||
![]() |
adb3bb3653 | ||
![]() |
2390a7f365 | ||
![]() |
2c1c395c28 | ||
![]() |
1d962aeb65 | ||
![]() |
02c170b961 | ||
![]() |
b9fa32444a | ||
![]() |
03d8abe1ba | ||
![]() |
0364cd8db5 | ||
![]() |
3b17e570df | ||
![]() |
223c01d842 | ||
![]() |
d51b2ad675 | ||
![]() |
fefbe02d44 | ||
![]() |
9329bc4ca0 | ||
![]() |
b8fbe758d8 | ||
![]() |
5bdf450f78 | ||
![]() |
61476f4f2c | ||
![]() |
cab60bcd0c | ||
![]() |
c0394232f3 | ||
![]() |
a5d9e89d08 | ||
![]() |
f43b26f250 | ||
![]() |
58b32bbeff | ||
![]() |
6d0a465390 | ||
![]() |
a5d334bbf7 | ||
![]() |
a77fd4892e | ||
![]() |
75e8d49af1 | ||
![]() |
5a56d0ec1d | ||
![]() |
ecd076c5e4 | ||
![]() |
e695bb55c8 | ||
![]() |
aab2fd5ec3 | ||
![]() |
0eb5ca67cd | ||
![]() |
df04fe3258 | ||
![]() |
4c5e364d90 | ||
![]() |
d207c37c33 | ||
![]() |
f26826d949 | ||
![]() |
483d822272 | ||
![]() |
92988d60a7 | ||
![]() |
16dba3fa85 | ||
![]() |
2a88ae559e | ||
![]() |
853d6cda25 | ||
![]() |
c92aa30663 | ||
![]() |
92a47f14bb | ||
![]() |
2d68b37dd5 | ||
![]() |
4de3871a78 | ||
![]() |
9c755d8fd4 | ||
![]() |
1e5f0a5136 | ||
![]() |
897433ecba | ||
![]() |
89625010e5 | ||
![]() |
e435f6eb67 | ||
![]() |
5a67d73a37 | ||
![]() |
638a3025df | ||
![]() |
b5c1afcb84 | ||
![]() |
4c33a9d732 | ||
![]() |
58de7fe9a3 | ||
![]() |
ed461a0ad8 | ||
![]() |
3e702c8ca4 | ||
![]() |
7391aa2d7e | ||
![]() |
0e7e0086cd | ||
![]() |
6f8380b442 | ||
![]() |
e60a6cfa19 | ||
![]() |
ff6d415c67 | ||
![]() |
d3629d9f32 | ||
![]() |
61e2ce5faf | ||
![]() |
b0fdbebd56 | ||
![]() |
fc0278c91f | ||
![]() |
1f23361a5d | ||
![]() |
4a89fba6f9 | ||
![]() |
dd13e99967 | ||
![]() |
24652d82ab | ||
![]() |
1ddc1ebc6b | ||
![]() |
7d8da47309 | ||
![]() |
ceb3985a99 | ||
![]() |
536b31305a | ||
![]() |
2d6b80470f | ||
![]() |
2365e2e8cf | ||
![]() |
5c12fa0daa | ||
![]() |
2925e0617c | ||
![]() |
900714a3ee | ||
![]() |
5488389244 | ||
![]() |
438c4acf07 | ||
![]() |
c9d78aa78c | ||
![]() |
ba4cc373c8 | ||
![]() |
c97b1c60b0 | ||
![]() |
0e6a48c688 | ||
![]() |
5776b9f17d | ||
![]() |
9007d37de1 | ||
![]() |
7c2765fbff | ||
![]() |
75f465bf7e | ||
![]() |
7e387f93d6 | ||
![]() |
9e832aaaa6 | ||
![]() |
5dc93aeeb1 | ||
![]() |
45d63e22d5 | ||
![]() |
6edef444a7 | ||
![]() |
6f1f9bdd45 | ||
![]() |
1d075a7dd0 | ||
![]() |
4236d62b44 | ||
![]() |
71a6ea1c10 | ||
![]() |
e0586df602 | ||
![]() |
88df9c8ab4 | ||
![]() |
ad102b3840 | ||
![]() |
496bd3dddf | ||
![]() |
c98f50115d | ||
![]() |
9b2544c923 | ||
![]() |
a9e061270b | ||
![]() |
3f49f6c047 | ||
![]() |
b2d7bc40dc | ||
![]() |
309989be89 | ||
![]() |
90859b82e2 | ||
![]() |
6b0d7c77f0 | ||
![]() |
edf44f4158 | ||
![]() |
f7e336eaa6 | ||
![]() |
db40b2fc32 | ||
![]() |
9801810552 | ||
![]() |
07fa844c43 | ||
![]() |
15b4975681 | ||
![]() |
d996a4a9a9 | ||
![]() |
df9363610c | ||
![]() |
a1a835cf54 | ||
![]() |
ca01e9a537 | ||
![]() |
270758417b | ||
![]() |
6e6625e1ab | ||
![]() |
5ec7d07283 | ||
![]() |
693441e56f | ||
![]() |
d2d788631e | ||
![]() |
524a1a7587 | ||
![]() |
a85808e325 | ||
![]() |
8dd80e0e3c | ||
![]() |
1007283da5 | ||
![]() |
4cc4f070f5 | ||
![]() |
f6fbecf963 | ||
![]() |
a533b7a746 | ||
![]() |
458e47f981 | ||
![]() |
d22ee7179d | ||
![]() |
f975654ae7 | ||
![]() |
a678c6fd0b | ||
![]() |
bd00453cef | ||
![]() |
8257165562 | ||
![]() |
fcaabb3d33 | ||
![]() |
edfb967b10 | ||
![]() |
ced870c588 | ||
![]() |
dc15b9c28e | ||
![]() |
b41cbe9885 | ||
![]() |
3385893b77 | ||
![]() |
36db302cc8 | ||
![]() |
9cc47ca737 | ||
![]() |
2fb66fd866 | ||
![]() |
f32411e394 | ||
![]() |
dd8597cb46 | ||
![]() |
6e74ee7b64 | ||
![]() |
d4075fb262 | ||
![]() |
a12c4da0ca | ||
![]() |
1ee7c483a7 | ||
![]() |
51b5796916 | ||
![]() |
f0d58ab7a7 | ||
![]() |
ce710f1e0b | ||
![]() |
2e35190aff | ||
![]() |
20e3c91ebd | ||
![]() |
bf1092ec80 | ||
![]() |
b2f2afaf0c | ||
![]() |
1c81e8ad68 | ||
![]() |
1c2bce9292 | ||
![]() |
5c5f839119 | ||
![]() |
a7d5e898ba | ||
![]() |
7a6b13cb0d | ||
![]() |
51b2d0b4f8 | ||
![]() |
2ad1f7fd02 | ||
![]() |
7e3841e172 | ||
![]() |
2ec0a504da | ||
![]() |
e71c7e1f5e | ||
![]() |
6c9d4a6d15 | ||
![]() |
d6c185fdf4 | ||
![]() |
aae64dba62 | ||
![]() |
febd7e551b | ||
![]() |
047111b00f | ||
![]() |
136ed12ec5 | ||
![]() |
b6d60c36a5 | ||
![]() |
832337f26c | ||
![]() |
5839df39b5 | ||
![]() |
6f8f23238a | ||
![]() |
4e765398cc | ||
![]() |
8255bdf3d5 |
18
.coveragerc
18
.coveragerc
@@ -29,6 +29,7 @@ omit =
|
|||||||
homeassistant/components/airly/air_quality.py
|
homeassistant/components/airly/air_quality.py
|
||||||
homeassistant/components/airly/sensor.py
|
homeassistant/components/airly/sensor.py
|
||||||
homeassistant/components/airly/const.py
|
homeassistant/components/airly/const.py
|
||||||
|
homeassistant/components/airvisual/__init__.py
|
||||||
homeassistant/components/airvisual/sensor.py
|
homeassistant/components/airvisual/sensor.py
|
||||||
homeassistant/components/aladdin_connect/cover.py
|
homeassistant/components/aladdin_connect/cover.py
|
||||||
homeassistant/components/alarmdecoder/*
|
homeassistant/components/alarmdecoder/*
|
||||||
@@ -58,14 +59,13 @@ omit =
|
|||||||
homeassistant/components/arwn/sensor.py
|
homeassistant/components/arwn/sensor.py
|
||||||
homeassistant/components/asterisk_cdr/mailbox.py
|
homeassistant/components/asterisk_cdr/mailbox.py
|
||||||
homeassistant/components/asterisk_mbox/*
|
homeassistant/components/asterisk_mbox/*
|
||||||
homeassistant/components/asuswrt/device_tracker.py
|
|
||||||
homeassistant/components/aten_pe/*
|
homeassistant/components/aten_pe/*
|
||||||
homeassistant/components/atome/*
|
homeassistant/components/atome/*
|
||||||
homeassistant/components/august/*
|
|
||||||
homeassistant/components/aurora_abb_powerone/sensor.py
|
homeassistant/components/aurora_abb_powerone/sensor.py
|
||||||
homeassistant/components/automatic/device_tracker.py
|
homeassistant/components/automatic/device_tracker.py
|
||||||
homeassistant/components/avea/light.py
|
homeassistant/components/avea/light.py
|
||||||
homeassistant/components/avion/light.py
|
homeassistant/components/avion/light.py
|
||||||
|
homeassistant/components/avri/sensor.py
|
||||||
homeassistant/components/azure_event_hub/*
|
homeassistant/components/azure_event_hub/*
|
||||||
homeassistant/components/azure_service_bus/*
|
homeassistant/components/azure_service_bus/*
|
||||||
homeassistant/components/baidu/tts.py
|
homeassistant/components/baidu/tts.py
|
||||||
@@ -108,7 +108,6 @@ omit =
|
|||||||
homeassistant/components/canary/alarm_control_panel.py
|
homeassistant/components/canary/alarm_control_panel.py
|
||||||
homeassistant/components/canary/camera.py
|
homeassistant/components/canary/camera.py
|
||||||
homeassistant/components/cast/*
|
homeassistant/components/cast/*
|
||||||
homeassistant/components/cert_expiry/sensor.py
|
|
||||||
homeassistant/components/cert_expiry/helper.py
|
homeassistant/components/cert_expiry/helper.py
|
||||||
homeassistant/components/channels/*
|
homeassistant/components/channels/*
|
||||||
homeassistant/components/cisco_ios/device_tracker.py
|
homeassistant/components/cisco_ios/device_tracker.py
|
||||||
@@ -150,7 +149,6 @@ omit =
|
|||||||
homeassistant/components/dht/sensor.py
|
homeassistant/components/dht/sensor.py
|
||||||
homeassistant/components/digital_ocean/*
|
homeassistant/components/digital_ocean/*
|
||||||
homeassistant/components/digitalloggers/switch.py
|
homeassistant/components/digitalloggers/switch.py
|
||||||
homeassistant/components/directv/media_player.py
|
|
||||||
homeassistant/components/discogs/sensor.py
|
homeassistant/components/discogs/sensor.py
|
||||||
homeassistant/components/discord/notify.py
|
homeassistant/components/discord/notify.py
|
||||||
homeassistant/components/dlib_face_detect/image_processing.py
|
homeassistant/components/dlib_face_detect/image_processing.py
|
||||||
@@ -180,6 +178,7 @@ omit =
|
|||||||
homeassistant/components/ecobee/weather.py
|
homeassistant/components/ecobee/weather.py
|
||||||
homeassistant/components/econet/*
|
homeassistant/components/econet/*
|
||||||
homeassistant/components/ecovacs/*
|
homeassistant/components/ecovacs/*
|
||||||
|
homeassistant/components/edl21/*
|
||||||
homeassistant/components/eddystone_temperature/sensor.py
|
homeassistant/components/eddystone_temperature/sensor.py
|
||||||
homeassistant/components/edimax/switch.py
|
homeassistant/components/edimax/switch.py
|
||||||
homeassistant/components/egardia/*
|
homeassistant/components/egardia/*
|
||||||
@@ -218,6 +217,7 @@ omit =
|
|||||||
homeassistant/components/eufy/*
|
homeassistant/components/eufy/*
|
||||||
homeassistant/components/everlights/light.py
|
homeassistant/components/everlights/light.py
|
||||||
homeassistant/components/evohome/*
|
homeassistant/components/evohome/*
|
||||||
|
homeassistant/components/ezviz/*
|
||||||
homeassistant/components/familyhub/camera.py
|
homeassistant/components/familyhub/camera.py
|
||||||
homeassistant/components/fastdotcom/*
|
homeassistant/components/fastdotcom/*
|
||||||
homeassistant/components/ffmpeg/camera.py
|
homeassistant/components/ffmpeg/camera.py
|
||||||
@@ -314,6 +314,7 @@ omit =
|
|||||||
homeassistant/components/hydrawise/*
|
homeassistant/components/hydrawise/*
|
||||||
homeassistant/components/hyperion/light.py
|
homeassistant/components/hyperion/light.py
|
||||||
homeassistant/components/ialarm/alarm_control_panel.py
|
homeassistant/components/ialarm/alarm_control_panel.py
|
||||||
|
homeassistant/components/iammeter/sensor.py
|
||||||
homeassistant/components/iaqualink/binary_sensor.py
|
homeassistant/components/iaqualink/binary_sensor.py
|
||||||
homeassistant/components/iaqualink/climate.py
|
homeassistant/components/iaqualink/climate.py
|
||||||
homeassistant/components/iaqualink/light.py
|
homeassistant/components/iaqualink/light.py
|
||||||
@@ -411,7 +412,9 @@ omit =
|
|||||||
homeassistant/components/mediaroom/media_player.py
|
homeassistant/components/mediaroom/media_player.py
|
||||||
homeassistant/components/melcloud/__init__.py
|
homeassistant/components/melcloud/__init__.py
|
||||||
homeassistant/components/melcloud/climate.py
|
homeassistant/components/melcloud/climate.py
|
||||||
|
homeassistant/components/melcloud/const.py
|
||||||
homeassistant/components/melcloud/sensor.py
|
homeassistant/components/melcloud/sensor.py
|
||||||
|
homeassistant/components/melcloud/water_heater.py
|
||||||
homeassistant/components/message_bird/notify.py
|
homeassistant/components/message_bird/notify.py
|
||||||
homeassistant/components/met/weather.py
|
homeassistant/components/met/weather.py
|
||||||
homeassistant/components/meteo_france/__init__.py
|
homeassistant/components/meteo_france/__init__.py
|
||||||
@@ -463,7 +466,6 @@ omit =
|
|||||||
homeassistant/components/nello/lock.py
|
homeassistant/components/nello/lock.py
|
||||||
homeassistant/components/nest/*
|
homeassistant/components/nest/*
|
||||||
homeassistant/components/netatmo/__init__.py
|
homeassistant/components/netatmo/__init__.py
|
||||||
homeassistant/components/netatmo/binary_sensor.py
|
|
||||||
homeassistant/components/netatmo/api.py
|
homeassistant/components/netatmo/api.py
|
||||||
homeassistant/components/netatmo/camera.py
|
homeassistant/components/netatmo/camera.py
|
||||||
homeassistant/components/netatmo/climate.py
|
homeassistant/components/netatmo/climate.py
|
||||||
@@ -480,6 +482,7 @@ omit =
|
|||||||
homeassistant/components/nissan_leaf/*
|
homeassistant/components/nissan_leaf/*
|
||||||
homeassistant/components/nmap_tracker/device_tracker.py
|
homeassistant/components/nmap_tracker/device_tracker.py
|
||||||
homeassistant/components/nmbs/sensor.py
|
homeassistant/components/nmbs/sensor.py
|
||||||
|
homeassistant/components/notion/__init__.py
|
||||||
homeassistant/components/notion/binary_sensor.py
|
homeassistant/components/notion/binary_sensor.py
|
||||||
homeassistant/components/notion/sensor.py
|
homeassistant/components/notion/sensor.py
|
||||||
homeassistant/components/noaa_tides/sensor.py
|
homeassistant/components/noaa_tides/sensor.py
|
||||||
@@ -538,10 +541,8 @@ omit =
|
|||||||
homeassistant/components/pioneer/media_player.py
|
homeassistant/components/pioneer/media_player.py
|
||||||
homeassistant/components/pjlink/media_player.py
|
homeassistant/components/pjlink/media_player.py
|
||||||
homeassistant/components/plaato/*
|
homeassistant/components/plaato/*
|
||||||
homeassistant/components/plex/__init__.py
|
|
||||||
homeassistant/components/plex/media_player.py
|
homeassistant/components/plex/media_player.py
|
||||||
homeassistant/components/plex/sensor.py
|
homeassistant/components/plex/sensor.py
|
||||||
homeassistant/components/plex/server.py
|
|
||||||
homeassistant/components/plugwise/*
|
homeassistant/components/plugwise/*
|
||||||
homeassistant/components/plum_lightpad/*
|
homeassistant/components/plum_lightpad/*
|
||||||
homeassistant/components/pocketcasts/sensor.py
|
homeassistant/components/pocketcasts/sensor.py
|
||||||
@@ -565,6 +566,7 @@ omit =
|
|||||||
homeassistant/components/qnap/sensor.py
|
homeassistant/components/qnap/sensor.py
|
||||||
homeassistant/components/qrcode/image_processing.py
|
homeassistant/components/qrcode/image_processing.py
|
||||||
homeassistant/components/quantum_gateway/device_tracker.py
|
homeassistant/components/quantum_gateway/device_tracker.py
|
||||||
|
homeassistant/components/qvr_pro/*
|
||||||
homeassistant/components/qwikswitch/*
|
homeassistant/components/qwikswitch/*
|
||||||
homeassistant/components/rachio/*
|
homeassistant/components/rachio/*
|
||||||
homeassistant/components/radarr/sensor.py
|
homeassistant/components/radarr/sensor.py
|
||||||
@@ -698,6 +700,7 @@ omit =
|
|||||||
homeassistant/components/tado/device_tracker.py
|
homeassistant/components/tado/device_tracker.py
|
||||||
homeassistant/components/tahoma/*
|
homeassistant/components/tahoma/*
|
||||||
homeassistant/components/tank_utility/sensor.py
|
homeassistant/components/tank_utility/sensor.py
|
||||||
|
homeassistant/components/tankerkoenig/*
|
||||||
homeassistant/components/tapsaff/binary_sensor.py
|
homeassistant/components/tapsaff/binary_sensor.py
|
||||||
homeassistant/components/tautulli/sensor.py
|
homeassistant/components/tautulli/sensor.py
|
||||||
homeassistant/components/ted5000/sensor.py
|
homeassistant/components/ted5000/sensor.py
|
||||||
@@ -842,6 +845,7 @@ omit =
|
|||||||
homeassistant/components/zha/core/helpers.py
|
homeassistant/components/zha/core/helpers.py
|
||||||
homeassistant/components/zha/core/patches.py
|
homeassistant/components/zha/core/patches.py
|
||||||
homeassistant/components/zha/core/registries.py
|
homeassistant/components/zha/core/registries.py
|
||||||
|
homeassistant/components/zha/core/typing.py
|
||||||
homeassistant/components/zha/entity.py
|
homeassistant/components/zha/entity.py
|
||||||
homeassistant/components/zha/light.py
|
homeassistant/components/zha/light.py
|
||||||
homeassistant/components/zha/sensor.py
|
homeassistant/components/zha/sensor.py
|
||||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,7 +1,7 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Report a bug with the UI, Frontend or Lovelace
|
- name: Report a bug with the UI, Frontend or Lovelace
|
||||||
url: https://github.com/home-assistant/home-assistant-polymer/issues
|
url: https://github.com/home-assistant/frontend/issues
|
||||||
about: This is the issue tracker for our backend. Please report issues with the UI in the frontend repository.
|
about: This is the issue tracker for our backend. Please report issues with the UI in the frontend repository.
|
||||||
- name: Report incorrect or missing information on our website
|
- name: Report incorrect or missing information on our website
|
||||||
url: https://github.com/home-assistant/home-assistant.io/issues
|
url: https://github.com/home-assistant/home-assistant.io/issues
|
||||||
|
@@ -41,6 +41,11 @@ repos:
|
|||||||
rev: v2.4.0
|
rev: v2.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-json
|
- id: check-json
|
||||||
|
- id: no-commit-to-branch
|
||||||
|
args:
|
||||||
|
- --branch=dev
|
||||||
|
- --branch=master
|
||||||
|
- --branch=rc
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
# Run mypy through our wrapper script in order to get the possible
|
# Run mypy through our wrapper script in order to get the possible
|
||||||
|
18
CODEOWNERS
18
CODEOWNERS
@@ -41,6 +41,7 @@ homeassistant/components/auth/* @home-assistant/core
|
|||||||
homeassistant/components/automatic/* @armills
|
homeassistant/components/automatic/* @armills
|
||||||
homeassistant/components/automation/* @home-assistant/core
|
homeassistant/components/automation/* @home-assistant/core
|
||||||
homeassistant/components/avea/* @pattyland
|
homeassistant/components/avea/* @pattyland
|
||||||
|
homeassistant/components/avri/* @timvancann
|
||||||
homeassistant/components/awair/* @danielsjf
|
homeassistant/components/awair/* @danielsjf
|
||||||
homeassistant/components/aws/* @awarecan @robbiet480
|
homeassistant/components/aws/* @awarecan @robbiet480
|
||||||
homeassistant/components/axis/* @kane610
|
homeassistant/components/axis/* @kane610
|
||||||
@@ -51,6 +52,7 @@ homeassistant/components/bitcoin/* @fabaff
|
|||||||
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
homeassistant/components/bizkaibus/* @UgaitzEtxebarria
|
||||||
homeassistant/components/blink/* @fronzbot
|
homeassistant/components/blink/* @fronzbot
|
||||||
homeassistant/components/bmw_connected_drive/* @gerard33
|
homeassistant/components/bmw_connected_drive/* @gerard33
|
||||||
|
homeassistant/components/bom/* @maddenp
|
||||||
homeassistant/components/braviatv/* @robbiet480
|
homeassistant/components/braviatv/* @robbiet480
|
||||||
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
homeassistant/components/broadlink/* @danielhiversen @felipediel
|
||||||
homeassistant/components/brother/* @bieniu
|
homeassistant/components/brother/* @bieniu
|
||||||
@@ -68,6 +70,7 @@ homeassistant/components/config/* @home-assistant/core
|
|||||||
homeassistant/components/configurator/* @home-assistant/core
|
homeassistant/components/configurator/* @home-assistant/core
|
||||||
homeassistant/components/conversation/* @home-assistant/core
|
homeassistant/components/conversation/* @home-assistant/core
|
||||||
homeassistant/components/coolmaster/* @OnFreund
|
homeassistant/components/coolmaster/* @OnFreund
|
||||||
|
homeassistant/components/coronavirus/* @home_assistant/core
|
||||||
homeassistant/components/counter/* @fabaff
|
homeassistant/components/counter/* @fabaff
|
||||||
homeassistant/components/cover/* @home-assistant/core
|
homeassistant/components/cover/* @home-assistant/core
|
||||||
homeassistant/components/cpuspeed/* @fabaff
|
homeassistant/components/cpuspeed/* @fabaff
|
||||||
@@ -80,6 +83,7 @@ homeassistant/components/demo/* @home-assistant/core
|
|||||||
homeassistant/components/derivative/* @afaucogney
|
homeassistant/components/derivative/* @afaucogney
|
||||||
homeassistant/components/device_automation/* @home-assistant/core
|
homeassistant/components/device_automation/* @home-assistant/core
|
||||||
homeassistant/components/digital_ocean/* @fabaff
|
homeassistant/components/digital_ocean/* @fabaff
|
||||||
|
homeassistant/components/directv/* @ctalkington
|
||||||
homeassistant/components/discogs/* @thibmaek
|
homeassistant/components/discogs/* @thibmaek
|
||||||
homeassistant/components/doorbird/* @oblogic7
|
homeassistant/components/doorbird/* @oblogic7
|
||||||
homeassistant/components/dsmr_reader/* @depl0y
|
homeassistant/components/dsmr_reader/* @depl0y
|
||||||
@@ -88,11 +92,13 @@ homeassistant/components/dynalite/* @ziv1234
|
|||||||
homeassistant/components/dyson/* @etheralm
|
homeassistant/components/dyson/* @etheralm
|
||||||
homeassistant/components/ecobee/* @marthoc
|
homeassistant/components/ecobee/* @marthoc
|
||||||
homeassistant/components/ecovacs/* @OverloadUT
|
homeassistant/components/ecovacs/* @OverloadUT
|
||||||
|
homeassistant/components/edl21/* @mtdcr
|
||||||
homeassistant/components/egardia/* @jeroenterheerdt
|
homeassistant/components/egardia/* @jeroenterheerdt
|
||||||
homeassistant/components/eight_sleep/* @mezz64
|
homeassistant/components/eight_sleep/* @mezz64
|
||||||
homeassistant/components/elgato/* @frenck
|
homeassistant/components/elgato/* @frenck
|
||||||
homeassistant/components/elv/* @majuss
|
homeassistant/components/elv/* @majuss
|
||||||
homeassistant/components/emby/* @mezz64
|
homeassistant/components/emby/* @mezz64
|
||||||
|
homeassistant/components/emoncms/* @borpin
|
||||||
homeassistant/components/enigma2/* @fbradyirl
|
homeassistant/components/enigma2/* @fbradyirl
|
||||||
homeassistant/components/enocean/* @bdurrer
|
homeassistant/components/enocean/* @bdurrer
|
||||||
homeassistant/components/entur_public_transport/* @hfurubotten
|
homeassistant/components/entur_public_transport/* @hfurubotten
|
||||||
@@ -103,6 +109,7 @@ homeassistant/components/eq3btsmart/* @rytilahti
|
|||||||
homeassistant/components/esphome/* @OttoWinter
|
homeassistant/components/esphome/* @OttoWinter
|
||||||
homeassistant/components/essent/* @TheLastProject
|
homeassistant/components/essent/* @TheLastProject
|
||||||
homeassistant/components/evohome/* @zxdavb
|
homeassistant/components/evohome/* @zxdavb
|
||||||
|
homeassistant/components/ezviz/* @baqs
|
||||||
homeassistant/components/fastdotcom/* @rohankapoorcom
|
homeassistant/components/fastdotcom/* @rohankapoorcom
|
||||||
homeassistant/components/file/* @fabaff
|
homeassistant/components/file/* @fabaff
|
||||||
homeassistant/components/filter/* @dgomes
|
homeassistant/components/filter/* @dgomes
|
||||||
@@ -135,6 +142,7 @@ homeassistant/components/google_translate/* @awarecan
|
|||||||
homeassistant/components/google_travel_time/* @robbiet480
|
homeassistant/components/google_travel_time/* @robbiet480
|
||||||
homeassistant/components/gpsd/* @fabaff
|
homeassistant/components/gpsd/* @fabaff
|
||||||
homeassistant/components/greeneye_monitor/* @jkeljo
|
homeassistant/components/greeneye_monitor/* @jkeljo
|
||||||
|
homeassistant/components/griddy/* @bdraco
|
||||||
homeassistant/components/group/* @home-assistant/core
|
homeassistant/components/group/* @home-assistant/core
|
||||||
homeassistant/components/growatt_server/* @indykoning
|
homeassistant/components/growatt_server/* @indykoning
|
||||||
homeassistant/components/gtfs/* @robbiet480
|
homeassistant/components/gtfs/* @robbiet480
|
||||||
@@ -147,7 +155,6 @@ homeassistant/components/hikvision/* @mezz64
|
|||||||
homeassistant/components/hikvisioncam/* @fbradyirl
|
homeassistant/components/hikvisioncam/* @fbradyirl
|
||||||
homeassistant/components/hisense_aehw4a1/* @bannhead
|
homeassistant/components/hisense_aehw4a1/* @bannhead
|
||||||
homeassistant/components/history/* @home-assistant/core
|
homeassistant/components/history/* @home-assistant/core
|
||||||
homeassistant/components/history_graph/* @andrey-git
|
|
||||||
homeassistant/components/hive/* @Rendili @KJonline
|
homeassistant/components/hive/* @Rendili @KJonline
|
||||||
homeassistant/components/homeassistant/* @home-assistant/core
|
homeassistant/components/homeassistant/* @home-assistant/core
|
||||||
homeassistant/components/homekit_controller/* @Jc2k
|
homeassistant/components/homekit_controller/* @Jc2k
|
||||||
@@ -159,6 +166,7 @@ homeassistant/components/http/* @home-assistant/core
|
|||||||
homeassistant/components/huawei_lte/* @scop
|
homeassistant/components/huawei_lte/* @scop
|
||||||
homeassistant/components/huawei_router/* @abmantis
|
homeassistant/components/huawei_router/* @abmantis
|
||||||
homeassistant/components/hue/* @balloob
|
homeassistant/components/hue/* @balloob
|
||||||
|
homeassistant/components/iammeter/* @lewei50
|
||||||
homeassistant/components/iaqualink/* @flz
|
homeassistant/components/iaqualink/* @flz
|
||||||
homeassistant/components/icloud/* @Quentame
|
homeassistant/components/icloud/* @Quentame
|
||||||
homeassistant/components/ign_sismologia/* @exxamalte
|
homeassistant/components/ign_sismologia/* @exxamalte
|
||||||
@@ -276,6 +284,7 @@ homeassistant/components/pvoutput/* @fabaff
|
|||||||
homeassistant/components/qld_bushfire/* @exxamalte
|
homeassistant/components/qld_bushfire/* @exxamalte
|
||||||
homeassistant/components/qnap/* @colinodell
|
homeassistant/components/qnap/* @colinodell
|
||||||
homeassistant/components/quantum_gateway/* @cisasteelersfan
|
homeassistant/components/quantum_gateway/* @cisasteelersfan
|
||||||
|
homeassistant/components/qvr_pro/* @oblogic7
|
||||||
homeassistant/components/qwikswitch/* @kellerza
|
homeassistant/components/qwikswitch/* @kellerza
|
||||||
homeassistant/components/rainbird/* @konikvranik
|
homeassistant/components/rainbird/* @konikvranik
|
||||||
homeassistant/components/raincloud/* @vanstinator
|
homeassistant/components/raincloud/* @vanstinator
|
||||||
@@ -286,6 +295,7 @@ homeassistant/components/repetier/* @MTrab
|
|||||||
homeassistant/components/rfxtrx/* @danielhiversen
|
homeassistant/components/rfxtrx/* @danielhiversen
|
||||||
homeassistant/components/ring/* @balloob
|
homeassistant/components/ring/* @balloob
|
||||||
homeassistant/components/rmvtransport/* @cgtobi
|
homeassistant/components/rmvtransport/* @cgtobi
|
||||||
|
homeassistant/components/roku/* @ctalkington
|
||||||
homeassistant/components/roomba/* @pschmitt
|
homeassistant/components/roomba/* @pschmitt
|
||||||
homeassistant/components/safe_mode/* @home-assistant/core
|
homeassistant/components/safe_mode/* @home-assistant/core
|
||||||
homeassistant/components/saj/* @fredericvl
|
homeassistant/components/saj/* @fredericvl
|
||||||
@@ -346,6 +356,7 @@ homeassistant/components/synology_srm/* @aerialls
|
|||||||
homeassistant/components/syslog/* @fabaff
|
homeassistant/components/syslog/* @fabaff
|
||||||
homeassistant/components/tado/* @michaelarnauts
|
homeassistant/components/tado/* @michaelarnauts
|
||||||
homeassistant/components/tahoma/* @philklei
|
homeassistant/components/tahoma/* @philklei
|
||||||
|
homeassistant/components/tankerkoenig/* @guillempages
|
||||||
homeassistant/components/tautulli/* @ludeeus
|
homeassistant/components/tautulli/* @ludeeus
|
||||||
homeassistant/components/tellduslive/* @fredrike
|
homeassistant/components/tellduslive/* @fredrike
|
||||||
homeassistant/components/template/* @PhracturedBlue @tetienne
|
homeassistant/components/template/* @PhracturedBlue @tetienne
|
||||||
@@ -365,7 +376,7 @@ homeassistant/components/traccar/* @ludeeus
|
|||||||
homeassistant/components/tradfri/* @ggravlingen
|
homeassistant/components/tradfri/* @ggravlingen
|
||||||
homeassistant/components/trafikverket_train/* @endor-force
|
homeassistant/components/trafikverket_train/* @endor-force
|
||||||
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
homeassistant/components/transmission/* @engrbm87 @JPHutchins
|
||||||
homeassistant/components/tts/* @robbiet480
|
homeassistant/components/tts/* @pvizeli
|
||||||
homeassistant/components/twentemilieu/* @frenck
|
homeassistant/components/twentemilieu/* @frenck
|
||||||
homeassistant/components/twilio_call/* @robbiet480
|
homeassistant/components/twilio_call/* @robbiet480
|
||||||
homeassistant/components/twilio_sms/* @robbiet480
|
homeassistant/components/twilio_sms/* @robbiet480
|
||||||
@@ -375,7 +386,7 @@ homeassistant/components/unifiled/* @florisvdk
|
|||||||
homeassistant/components/upc_connect/* @pvizeli
|
homeassistant/components/upc_connect/* @pvizeli
|
||||||
homeassistant/components/upcloud/* @scop
|
homeassistant/components/upcloud/* @scop
|
||||||
homeassistant/components/updater/* @home-assistant/core
|
homeassistant/components/updater/* @home-assistant/core
|
||||||
homeassistant/components/upnp/* @robbiet480
|
homeassistant/components/upnp/* @StevenLooman
|
||||||
homeassistant/components/uptimerobot/* @ludeeus
|
homeassistant/components/uptimerobot/* @ludeeus
|
||||||
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||||
homeassistant/components/utility_meter/* @dgomes
|
homeassistant/components/utility_meter/* @dgomes
|
||||||
@@ -392,7 +403,6 @@ homeassistant/components/vlc_telnet/* @rodripf
|
|||||||
homeassistant/components/waqi/* @andrey-git
|
homeassistant/components/waqi/* @andrey-git
|
||||||
homeassistant/components/watson_tts/* @rutkai
|
homeassistant/components/watson_tts/* @rutkai
|
||||||
homeassistant/components/weather/* @fabaff
|
homeassistant/components/weather/* @fabaff
|
||||||
homeassistant/components/weblink/* @home-assistant/core
|
|
||||||
homeassistant/components/webostv/* @bendavid
|
homeassistant/components/webostv/* @bendavid
|
||||||
homeassistant/components/websocket_api/* @home-assistant/core
|
homeassistant/components/websocket_api/* @home-assistant/core
|
||||||
homeassistant/components/wemo/* @sqldiablo
|
homeassistant/components/wemo/* @sqldiablo
|
||||||
|
@@ -148,7 +148,7 @@ stages:
|
|||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
pytest --timeout=9 --durations=10 -n auto --dist=loadfile --cov homeassistant --cov-report html -qq -o console_output_style=count -p no:sugar tests
|
||||||
codecov --token $(codecovToken)
|
#codecov --token $(codecovToken)
|
||||||
script/check_dirty
|
script/check_dirty
|
||||||
displayName: 'Run pytest for python $(python.container) / coverage'
|
displayName: 'Run pytest for python $(python.container) / coverage'
|
||||||
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
condition: and(succeeded(), eq(variables['python.container'], variables['PythonMain']))
|
||||||
|
@@ -41,7 +41,7 @@ stages:
|
|||||||
jq curl
|
jq curl
|
||||||
|
|
||||||
release="$(Build.SourceBranchName)"
|
release="$(Build.SourceBranchName)"
|
||||||
created_by="$(curl -s https://api.github.com/repos/home-assistant/home-assistant/releases/tags/${release} | jq --raw-output '.author.login')"
|
created_by="$(curl -s https://api.github.com/repos/home-assistant/core/releases/tags/${release} | jq --raw-output '.author.login')"
|
||||||
|
|
||||||
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten|frenck)$ ]]; then
|
if [[ "${created_by}" =~ ^(balloob|pvizeli|fabaff|robbiet480|bramkragten|frenck)$ ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
|
@@ -40,7 +40,7 @@ jobs:
|
|||||||
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
|
if [[ "$(Build.Reason)" =~ (Schedule|Manual) ]]; then
|
||||||
touch requirements_diff.txt
|
touch requirements_diff.txt
|
||||||
else
|
else
|
||||||
curl -s -o requirements_diff.txt https://raw.githubusercontent.com/home-assistant/home-assistant/master/requirements_all.txt
|
curl -s -o requirements_diff.txt https://raw.githubusercontent.com/home-assistant/core/master/requirements_all.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
requirement_files="requirements_wheels.txt requirements_diff.txt"
|
requirement_files="requirements_wheels.txt requirements_diff.txt"
|
||||||
|
12
homeassistant/components/abode/.translations/lv.json
Normal file
12
homeassistant/components/abode/.translations/lv.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "Parole",
|
||||||
|
"username": "E-pasta adrese"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,6 @@
|
|||||||
from asyncio import gather
|
from asyncio import gather
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
|
||||||
|
|
||||||
from abodepy import Abode
|
from abodepy import Abode
|
||||||
from abodepy.exceptions import AbodeException
|
from abodepy.exceptions import AbodeException
|
||||||
@@ -24,21 +23,13 @@ from homeassistant.helpers import config_validation as cv
|
|||||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
from .const import (
|
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER
|
||||||
ATTRIBUTION,
|
|
||||||
DEFAULT_CACHEDB,
|
|
||||||
DOMAIN,
|
|
||||||
SIGNAL_CAPTURE_IMAGE,
|
|
||||||
SIGNAL_TRIGGER_QUICK_ACTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF_POLLING = "polling"
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
SERVICE_SETTINGS = "change_setting"
|
SERVICE_SETTINGS = "change_setting"
|
||||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||||
SERVICE_TRIGGER = "trigger_quick_action"
|
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
|
||||||
|
|
||||||
ATTR_DEVICE_ID = "device_id"
|
ATTR_DEVICE_ID = "device_id"
|
||||||
ATTR_DEVICE_NAME = "device_name"
|
ATTR_DEVICE_NAME = "device_name"
|
||||||
@@ -53,8 +44,6 @@ ATTR_APP_TYPE = "app_type"
|
|||||||
ATTR_EVENT_BY = "event_by"
|
ATTR_EVENT_BY = "event_by"
|
||||||
ATTR_VALUE = "value"
|
ATTR_VALUE = "value"
|
||||||
|
|
||||||
ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str])
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.Schema(
|
DOMAIN: vol.Schema(
|
||||||
@@ -74,7 +63,7 @@ CHANGE_SETTING_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
|
CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
|
||||||
|
|
||||||
TRIGGER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
|
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
|
||||||
|
|
||||||
ABODE_PLATFORMS = [
|
ABODE_PLATFORMS = [
|
||||||
"alarm_control_panel",
|
"alarm_control_panel",
|
||||||
@@ -93,7 +82,6 @@ class AbodeSystem:
|
|||||||
|
|
||||||
def __init__(self, abode, polling):
|
def __init__(self, abode, polling):
|
||||||
"""Initialize the system."""
|
"""Initialize the system."""
|
||||||
|
|
||||||
self.abode = abode
|
self.abode = abode
|
||||||
self.polling = polling
|
self.polling = polling
|
||||||
self.entity_ids = set()
|
self.entity_ids = set()
|
||||||
@@ -130,7 +118,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
||||||
|
|
||||||
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||||
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for platform in ABODE_PLATFORMS:
|
for platform in ABODE_PLATFORMS:
|
||||||
@@ -149,7 +137,7 @@ async def async_unload_entry(hass, config_entry):
|
|||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
|
hass.services.async_remove(DOMAIN, SERVICE_SETTINGS)
|
||||||
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
|
hass.services.async_remove(DOMAIN, SERVICE_CAPTURE_IMAGE)
|
||||||
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER)
|
hass.services.async_remove(DOMAIN, SERVICE_TRIGGER_AUTOMATION)
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
|
|
||||||
@@ -180,7 +168,7 @@ def setup_hass_services(hass):
|
|||||||
try:
|
try:
|
||||||
hass.data[DOMAIN].abode.set_setting(setting, value)
|
hass.data[DOMAIN].abode.set_setting(setting, value)
|
||||||
except AbodeException as ex:
|
except AbodeException as ex:
|
||||||
_LOGGER.warning(ex)
|
LOGGER.warning(ex)
|
||||||
|
|
||||||
def capture_image(call):
|
def capture_image(call):
|
||||||
"""Capture a new image."""
|
"""Capture a new image."""
|
||||||
@@ -193,11 +181,11 @@ def setup_hass_services(hass):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for entity_id in target_entities:
|
for entity_id in target_entities:
|
||||||
signal = SIGNAL_CAPTURE_IMAGE.format(entity_id)
|
signal = f"abode_camera_capture_{entity_id}"
|
||||||
dispatcher_send(hass, signal)
|
dispatcher_send(hass, signal)
|
||||||
|
|
||||||
def trigger_quick_action(call):
|
def trigger_automation(call):
|
||||||
"""Trigger a quick action."""
|
"""Trigger an Abode automation."""
|
||||||
entity_ids = call.data.get(ATTR_ENTITY_ID, None)
|
entity_ids = call.data.get(ATTR_ENTITY_ID, None)
|
||||||
|
|
||||||
target_entities = [
|
target_entities = [
|
||||||
@@ -207,7 +195,7 @@ def setup_hass_services(hass):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for entity_id in target_entities:
|
for entity_id in target_entities:
|
||||||
signal = SIGNAL_TRIGGER_QUICK_ACTION.format(entity_id)
|
signal = f"abode_trigger_automation_{entity_id}"
|
||||||
dispatcher_send(hass, signal)
|
dispatcher_send(hass, signal)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.register(
|
||||||
@@ -219,7 +207,7 @@ def setup_hass_services(hass):
|
|||||||
)
|
)
|
||||||
|
|
||||||
hass.services.register(
|
hass.services.register(
|
||||||
DOMAIN, SERVICE_TRIGGER, trigger_quick_action, schema=TRIGGER_SCHEMA
|
DOMAIN, SERVICE_TRIGGER_AUTOMATION, trigger_automation, schema=AUTOMATION_SCHEMA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -232,7 +220,7 @@ async def setup_hass_events(hass):
|
|||||||
hass.data[DOMAIN].abode.events.stop()
|
hass.data[DOMAIN].abode.events.stop()
|
||||||
|
|
||||||
hass.data[DOMAIN].abode.logout()
|
hass.data[DOMAIN].abode.logout()
|
||||||
_LOGGER.info("Logged out of Abode")
|
LOGGER.info("Logged out of Abode")
|
||||||
|
|
||||||
if not hass.data[DOMAIN].polling:
|
if not hass.data[DOMAIN].polling:
|
||||||
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
|
await hass.async_add_executor_job(hass.data[DOMAIN].abode.events.start)
|
||||||
@@ -390,11 +378,14 @@ class AbodeAutomation(Entity):
|
|||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return {
|
return {
|
||||||
ATTR_ATTRIBUTION: ATTRIBUTION,
|
ATTR_ATTRIBUTION: ATTRIBUTION,
|
||||||
"automation_id": self._automation.automation_id,
|
"type": "CUE automation",
|
||||||
"type": self._automation.type,
|
|
||||||
"sub_type": self._automation.sub_type,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return a unique ID to use for this automation."""
|
||||||
|
return self._automation.automation_id
|
||||||
|
|
||||||
def _update_callback(self, device):
|
def _update_callback(self, device):
|
||||||
"""Update the automation state."""
|
"""Update the automation state."""
|
||||||
self._automation.refresh()
|
self._automation.refresh()
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
"""Support for Abode Security System alarm control panels."""
|
"""Support for Abode Security System alarm control panels."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import homeassistant.components.alarm_control_panel as alarm
|
import homeassistant.components.alarm_control_panel as alarm
|
||||||
from homeassistant.components.alarm_control_panel.const import (
|
from homeassistant.components.alarm_control_panel.const import (
|
||||||
SUPPORT_ALARM_ARM_AWAY,
|
SUPPORT_ALARM_ARM_AWAY,
|
||||||
@@ -16,8 +14,6 @@ from homeassistant.const import (
|
|||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import ATTRIBUTION, DOMAIN
|
from .const import ATTRIBUTION, DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
ICON = "mdi:security"
|
ICON = "mdi:security"
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +46,11 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanel):
|
|||||||
state = None
|
state = None
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def code_arm_required(self):
|
||||||
|
"""Whether the code is required for arm actions."""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Return the list of supported features."""
|
"""Return the list of supported features."""
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
"""Support for Abode Security System binary sensors."""
|
"""Support for Abode Security System binary sensors."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
import abodepy.helpers.timeline as TIMELINE
|
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|
||||||
|
|
||||||
from . import AbodeAutomation, AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN, SIGNAL_TRIGGER_QUICK_ACTION
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
@@ -30,13 +24,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
for device in data.abode.get_devices(generic_type=device_types):
|
for device in data.abode.get_devices(generic_type=device_types):
|
||||||
entities.append(AbodeBinarySensor(data, device))
|
entities.append(AbodeBinarySensor(data, device))
|
||||||
|
|
||||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION):
|
|
||||||
entities.append(
|
|
||||||
AbodeQuickActionBinarySensor(
|
|
||||||
data, automation, TIMELINE.AUTOMATION_EDIT_GROUP
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@@ -52,22 +39,3 @@ class AbodeBinarySensor(AbodeDevice, BinarySensorDevice):
|
|||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the class of the binary sensor."""
|
"""Return the class of the binary sensor."""
|
||||||
return self._device.generic_type
|
return self._device.generic_type
|
||||||
|
|
||||||
|
|
||||||
class AbodeQuickActionBinarySensor(AbodeAutomation, BinarySensorDevice):
|
|
||||||
"""A binary sensor implementation for Abode quick action automations."""
|
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
|
||||||
"""Subscribe Abode events."""
|
|
||||||
await super().async_added_to_hass()
|
|
||||||
signal = SIGNAL_TRIGGER_QUICK_ACTION.format(self.entity_id)
|
|
||||||
async_dispatcher_connect(self.hass, signal, self.trigger)
|
|
||||||
|
|
||||||
def trigger(self):
|
|
||||||
"""Trigger a quick automation."""
|
|
||||||
self._automation.trigger()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self):
|
|
||||||
"""Return True if the binary sensor is on."""
|
|
||||||
return self._automation.is_active
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
"""Support for Abode Security System cameras."""
|
"""Support for Abode Security System cameras."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
import abodepy.helpers.timeline as TIMELINE
|
import abodepy.helpers.timeline as TIMELINE
|
||||||
@@ -11,12 +10,10 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
|||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN, SIGNAL_CAPTURE_IMAGE
|
from .const import DOMAIN, LOGGER
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode camera devices."""
|
"""Set up Abode camera devices."""
|
||||||
@@ -50,7 +47,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
|||||||
self._capture_callback,
|
self._capture_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
signal = SIGNAL_CAPTURE_IMAGE.format(self.entity_id)
|
signal = f"abode_camera_capture_{self.entity_id}"
|
||||||
async_dispatcher_connect(self.hass, signal, self.capture)
|
async_dispatcher_connect(self.hass, signal, self.capture)
|
||||||
|
|
||||||
def capture(self):
|
def capture(self):
|
||||||
@@ -71,7 +68,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
|||||||
|
|
||||||
self._response.raise_for_status()
|
self._response.raise_for_status()
|
||||||
except requests.HTTPError as err:
|
except requests.HTTPError as err:
|
||||||
_LOGGER.warning("Failed to get camera image: %s", err)
|
LOGGER.warning("Failed to get camera image: %s", err)
|
||||||
self._response = None
|
self._response = None
|
||||||
else:
|
else:
|
||||||
self._response = None
|
self._response = None
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
"""Config flow for the Abode Security System component."""
|
"""Config flow for the Abode Security System component."""
|
||||||
import logging
|
|
||||||
|
|
||||||
from abodepy import Abode
|
from abodepy import Abode
|
||||||
from abodepy.exceptions import AbodeException
|
from abodepy.exceptions import AbodeException
|
||||||
from requests.exceptions import ConnectTimeout, HTTPError
|
from requests.exceptions import ConnectTimeout, HTTPError
|
||||||
@@ -10,12 +8,10 @@ from homeassistant import config_entries
|
|||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
|
|
||||||
from .const import DEFAULT_CACHEDB, DOMAIN # pylint: disable=unused-import
|
from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER # pylint: disable=unused-import
|
||||||
|
|
||||||
CONF_POLLING = "polling"
|
CONF_POLLING = "polling"
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Config flow for Abode."""
|
"""Config flow for Abode."""
|
||||||
@@ -32,7 +28,6 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle a flow initialized by the user."""
|
"""Handle a flow initialized by the user."""
|
||||||
|
|
||||||
if self._async_current_entries():
|
if self._async_current_entries():
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
@@ -50,7 +45,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
except (AbodeException, ConnectTimeout, HTTPError) as ex:
|
||||||
_LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
LOGGER.error("Unable to connect to Abode: %s", str(ex))
|
||||||
if ex.errcode == 400:
|
if ex.errcode == 400:
|
||||||
return self._show_form({"base": "invalid_credentials"})
|
return self._show_form({"base": "invalid_credentials"})
|
||||||
return self._show_form({"base": "connection_error"})
|
return self._show_form({"base": "connection_error"})
|
||||||
@@ -76,7 +71,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
async def async_step_import(self, import_config):
|
async def async_step_import(self, import_config):
|
||||||
"""Import a config entry from configuration.yaml."""
|
"""Import a config entry from configuration.yaml."""
|
||||||
if self._async_current_entries():
|
if self._async_current_entries():
|
||||||
_LOGGER.warning("Only one configuration of abode is allowed.")
|
LOGGER.warning("Only one configuration of abode is allowed.")
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
return await self.async_step_user(import_config)
|
return await self.async_step_user(import_config)
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
"""Constants for the Abode Security System component."""
|
"""Constants for the Abode Security System component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
DOMAIN = "abode"
|
DOMAIN = "abode"
|
||||||
ATTRIBUTION = "Data provided by goabode.com"
|
ATTRIBUTION = "Data provided by goabode.com"
|
||||||
|
|
||||||
DEFAULT_CACHEDB = "abodepy_cache.pickle"
|
DEFAULT_CACHEDB = "abodepy_cache.pickle"
|
||||||
|
|
||||||
SIGNAL_CAPTURE_IMAGE = "abode_camera_capture_{}"
|
|
||||||
SIGNAL_TRIGGER_QUICK_ACTION = "abode_trigger_quick_action_{}"
|
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
"""Support for Abode Security System covers."""
|
"""Support for Abode Security System covers."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.cover import CoverDevice
|
from homeassistant.components.cover import CoverDevice
|
||||||
@@ -8,8 +6,6 @@ from homeassistant.components.cover import CoverDevice
|
|||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode cover devices."""
|
"""Set up Abode cover devices."""
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
"""Support for Abode Security System lights."""
|
"""Support for Abode Security System lights."""
|
||||||
import logging
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
@@ -21,8 +20,6 @@ from homeassistant.util.color import (
|
|||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode light devices."""
|
"""Set up Abode light devices."""
|
||||||
@@ -45,15 +42,18 @@ class AbodeLight(AbodeDevice, Light):
|
|||||||
self._device.set_color_temp(
|
self._device.set_color_temp(
|
||||||
int(color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]))
|
int(color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]))
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if ATTR_HS_COLOR in kwargs and self._device.is_color_capable:
|
if ATTR_HS_COLOR in kwargs and self._device.is_color_capable:
|
||||||
self._device.set_color(kwargs[ATTR_HS_COLOR])
|
self._device.set_color(kwargs[ATTR_HS_COLOR])
|
||||||
|
return
|
||||||
|
|
||||||
if ATTR_BRIGHTNESS in kwargs and self._device.is_dimmable:
|
if ATTR_BRIGHTNESS in kwargs and self._device.is_dimmable:
|
||||||
# Convert Home Assistant brightness (0-255) to Abode brightness (0-99)
|
# Convert Home Assistant brightness (0-255) to Abode brightness (0-99)
|
||||||
# If 100 is sent to Abode, response is 99 causing an error
|
# If 100 is sent to Abode, response is 99 causing an error
|
||||||
self._device.set_level(ceil(kwargs[ATTR_BRIGHTNESS] * 99 / 255.0))
|
self._device.set_level(ceil(kwargs[ATTR_BRIGHTNESS] * 99 / 255.0))
|
||||||
else:
|
return
|
||||||
|
|
||||||
self._device.switch_on()
|
self._device.switch_on()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
"""Support for the Abode Security System locks."""
|
"""Support for the Abode Security System locks."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.components.lock import LockDevice
|
from homeassistant.components.lock import LockDevice
|
||||||
@@ -8,8 +6,6 @@ from homeassistant.components.lock import LockDevice
|
|||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode lock devices."""
|
"""Set up Abode lock devices."""
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Abode",
|
"name": "Abode",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||||
"requirements": ["abodepy==0.17.0"],
|
"requirements": ["abodepy==0.18.1"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@shred86"]
|
"codeowners": ["@shred86"]
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
"""Support for Abode Security System sensors."""
|
"""Support for Abode Security System sensors."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@@ -12,8 +10,6 @@ from homeassistant.const import (
|
|||||||
from . import AbodeDevice
|
from . import AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Sensor types: Name, icon
|
# Sensor types: Name, icon
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],
|
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],
|
||||||
@@ -44,9 +40,7 @@ class AbodeSensor(AbodeDevice):
|
|||||||
"""Initialize a sensor for an Abode device."""
|
"""Initialize a sensor for an Abode device."""
|
||||||
super().__init__(data, device)
|
super().__init__(data, device)
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
self._name = "{0} {1}".format(
|
self._name = f"{self._device.name} {SENSOR_TYPES[self._sensor_type][0]}"
|
||||||
self._device.name, SENSOR_TYPES[self._sensor_type][0]
|
|
||||||
)
|
|
||||||
self._device_class = SENSOR_TYPES[self._sensor_type][1]
|
self._device_class = SENSOR_TYPES[self._sensor_type][1]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -7,7 +7,7 @@ change_setting:
|
|||||||
fields:
|
fields:
|
||||||
setting: {description: Setting to change., example: beeper_mute}
|
setting: {description: Setting to change., example: beeper_mute}
|
||||||
value: {description: Value of the setting., example: '1'}
|
value: {description: Value of the setting., example: '1'}
|
||||||
trigger_quick_action:
|
trigger_automation:
|
||||||
description: Trigger an Abode quick action.
|
description: Trigger an Abode automation.
|
||||||
fields:
|
fields:
|
||||||
entity_id: {description: Entity id of the quick action to trigger., example: binary_sensor.home_quick_action}
|
entity_id: {description: Entity id of the automation to trigger., example: switch.my_automation}
|
@@ -1,18 +1,17 @@
|
|||||||
"""Support for Abode Security System switches."""
|
"""Support for Abode Security System switches."""
|
||||||
import logging
|
|
||||||
|
|
||||||
import abodepy.helpers.constants as CONST
|
import abodepy.helpers.constants as CONST
|
||||||
import abodepy.helpers.timeline as TIMELINE
|
import abodepy.helpers.timeline as TIMELINE
|
||||||
|
|
||||||
from homeassistant.components.switch import SwitchDevice
|
from homeassistant.components.switch import SwitchDevice
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
from . import AbodeAutomation, AbodeDevice
|
from . import AbodeAutomation, AbodeDevice
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
|
DEVICE_TYPES = [CONST.TYPE_SWITCH, CONST.TYPE_VALVE]
|
||||||
|
|
||||||
|
ICON = "mdi:robot"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up Abode switch devices."""
|
"""Set up Abode switch devices."""
|
||||||
@@ -24,7 +23,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
for device in data.abode.get_devices(generic_type=device_type):
|
for device in data.abode.get_devices(generic_type=device_type):
|
||||||
entities.append(AbodeSwitch(data, device))
|
entities.append(AbodeSwitch(data, device))
|
||||||
|
|
||||||
for automation in data.abode.get_automations(generic_type=CONST.TYPE_AUTOMATION):
|
for automation in data.abode.get_automations():
|
||||||
entities.append(
|
entities.append(
|
||||||
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
AbodeAutomationSwitch(data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)
|
||||||
)
|
)
|
||||||
@@ -52,15 +51,33 @@ class AbodeSwitch(AbodeDevice, SwitchDevice):
|
|||||||
class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice):
|
class AbodeAutomationSwitch(AbodeAutomation, SwitchDevice):
|
||||||
"""A switch implementation for Abode automations."""
|
"""A switch implementation for Abode automations."""
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Subscribe Abode events."""
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
signal = f"abode_trigger_automation_{self.entity_id}"
|
||||||
|
async_dispatcher_connect(self.hass, signal, self.trigger)
|
||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn on the device."""
|
"""Enable the automation."""
|
||||||
self._automation.set_active(True)
|
if self._automation.enable(True):
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn off the device."""
|
"""Disable the automation."""
|
||||||
self._automation.set_active(False)
|
if self._automation.enable(False):
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
def trigger(self):
|
||||||
|
"""Trigger the automation."""
|
||||||
|
self._automation.trigger()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
"""Return True if the binary sensor is on."""
|
"""Return True if the automation is enabled."""
|
||||||
return self._automation.is_active
|
return self._automation.is_enabled
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self):
|
||||||
|
"""Return the robot icon to match Home Assistant automations."""
|
||||||
|
return ICON
|
||||||
|
@@ -11,6 +11,7 @@ from homeassistant.components.adguard.const import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import TIME_MILLISECONDS, UNIT_PERCENTAGE
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
|
|||||||
"AdGuard DNS Queries Blocked Ratio",
|
"AdGuard DNS Queries Blocked Ratio",
|
||||||
"mdi:magnify-close",
|
"mdi:magnify-close",
|
||||||
"blocked_percentage",
|
"blocked_percentage",
|
||||||
"%",
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
async def _adguard_update(self) -> None:
|
||||||
@@ -206,7 +207,7 @@ class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
|
|||||||
"AdGuard Average Processing Speed",
|
"AdGuard Average Processing Speed",
|
||||||
"mdi:speedometer",
|
"mdi:speedometer",
|
||||||
"average_speed",
|
"average_speed",
|
||||||
"ms",
|
TIME_MILLISECONDS,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _adguard_update(self) -> None:
|
async def _adguard_update(self) -> None:
|
||||||
|
@@ -2,12 +2,14 @@
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
PRESSURE_HPA,
|
PRESSURE_HPA,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
@@ -26,21 +28,18 @@ ATTR_ICON = "icon"
|
|||||||
ATTR_LABEL = "label"
|
ATTR_LABEL = "label"
|
||||||
ATTR_UNIT = "unit"
|
ATTR_UNIT = "unit"
|
||||||
|
|
||||||
HUMI_PERCENT = "%"
|
|
||||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m³"
|
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
ATTR_API_PM1: {
|
ATTR_API_PM1: {
|
||||||
ATTR_DEVICE_CLASS: None,
|
ATTR_DEVICE_CLASS: None,
|
||||||
ATTR_ICON: "mdi:blur",
|
ATTR_ICON: "mdi:blur",
|
||||||
ATTR_LABEL: ATTR_API_PM1,
|
ATTR_LABEL: ATTR_API_PM1,
|
||||||
ATTR_UNIT: VOLUME_MICROGRAMS_PER_CUBIC_METER,
|
ATTR_UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
},
|
},
|
||||||
ATTR_API_HUMIDITY: {
|
ATTR_API_HUMIDITY: {
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
||||||
ATTR_ICON: None,
|
ATTR_ICON: None,
|
||||||
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
ATTR_LABEL: ATTR_API_HUMIDITY.capitalize(),
|
||||||
ATTR_UNIT: HUMI_PERCENT,
|
ATTR_UNIT: UNIT_PERCENTAGE,
|
||||||
},
|
},
|
||||||
ATTR_API_PRESSURE: {
|
ATTR_API_PRESSURE: {
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
||||||
|
23
homeassistant/components/airvisual/.translations/ca.json
Normal file
23
homeassistant/components/airvisual/.translations/ca.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Aquesta clau API ja est\u00e0 sent utilitzada."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Clau API inv\u00e0lida"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Clau API",
|
||||||
|
"latitude": "Latitud",
|
||||||
|
"longitude": "Longitud",
|
||||||
|
"show_on_map": "Mostra al mapa l'\u00e0rea geogr\u00e0fica monitoritzada"
|
||||||
|
},
|
||||||
|
"description": "Monitoritzaci\u00f3 de la qualitat de l'aire per ubicaci\u00f3 geogr\u00e0fica.",
|
||||||
|
"title": "Configura AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/airvisual/.translations/de.json
Normal file
21
homeassistant/components/airvisual/.translations/de.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Dieser API-Schl\u00fcssel wird bereits verwendet."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Ung\u00fcltiger API-Schl\u00fcssel"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API-Schl\u00fcssel",
|
||||||
|
"latitude": "Breitengrad",
|
||||||
|
"longitude": "L\u00e4ngengrad"
|
||||||
|
},
|
||||||
|
"title": "Konfigurieren Sie AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
33
homeassistant/components/airvisual/.translations/en.json
Normal file
33
homeassistant/components/airvisual/.translations/en.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This API key is already in use."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Invalid API key"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Key",
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"longitude": "Longitude"
|
||||||
|
},
|
||||||
|
"description": "Monitor air quality in a geographical location.",
|
||||||
|
"title": "Configure AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
},
|
||||||
|
"description": "Set various options for the AirVisual integration.",
|
||||||
|
"title": "Configure AirVisual"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
homeassistant/components/airvisual/.translations/es.json
Normal file
23
homeassistant/components/airvisual/.translations/es.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Esta clave API ya est\u00e1 en uso."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Clave API inv\u00e1lida"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Clave API",
|
||||||
|
"latitude": "Latitud",
|
||||||
|
"longitude": "Longitud",
|
||||||
|
"show_on_map": "Mostrar geograf\u00eda monitorizada en el mapa"
|
||||||
|
},
|
||||||
|
"description": "Monitorizar la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
|
||||||
|
"title": "Configurar AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
23
homeassistant/components/airvisual/.translations/it.json
Normal file
23
homeassistant/components/airvisual/.translations/it.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Questa chiave API \u00e8 gi\u00e0 in uso."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Chiave API non valida"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "Chiave API",
|
||||||
|
"latitude": "Latitudine",
|
||||||
|
"longitude": "Logitudine",
|
||||||
|
"show_on_map": "Mostra l'area geografica monitorata sulla mappa"
|
||||||
|
},
|
||||||
|
"description": "Monitorare la qualit\u00e0 dell'aria in una posizione geografica.",
|
||||||
|
"title": "Configura AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
21
homeassistant/components/airvisual/.translations/lb.json
Normal file
21
homeassistant/components/airvisual/.translations/lb.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "D\u00ebsen App Schl\u00ebssel g\u00ebtt scho benotzt"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Schl\u00ebssel",
|
||||||
|
"latitude": "Breedegrad",
|
||||||
|
"longitude": "L\u00e4ngegrad"
|
||||||
|
},
|
||||||
|
"title": "AirVisual konfigur\u00e9ieren"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
23
homeassistant/components/airvisual/.translations/no.json
Normal file
23
homeassistant/components/airvisual/.translations/no.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Denne API-n\u00f8kkelen er allerede i bruk."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Ugyldig API-n\u00f8kkel"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API-n\u00f8kkel",
|
||||||
|
"latitude": "Breddegrad",
|
||||||
|
"longitude": "Lengdegrad",
|
||||||
|
"show_on_map": "Vis overv\u00e5ket geografi p\u00e5 kartet"
|
||||||
|
},
|
||||||
|
"description": "Overv\u00e5k luftkvaliteten p\u00e5 et geografisk sted.",
|
||||||
|
"title": "Konfigurer AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
23
homeassistant/components/airvisual/.translations/ru.json
Normal file
23
homeassistant/components/airvisual/.translations/ru.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u042d\u0442\u043e\u0442 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "\u041a\u043b\u044e\u0447 API",
|
||||||
|
"latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
|
||||||
|
"longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
|
||||||
|
"show_on_map": "\u041f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0435\u043c\u0443\u044e \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u043d\u0430 \u043a\u0430\u0440\u0442\u0435"
|
||||||
|
},
|
||||||
|
"description": "\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u0439\u0442\u0435 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u0437\u0434\u0443\u0445\u0430 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0438.",
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u6b64 API \u5bc6\u9470\u5df2\u88ab\u4f7f\u7528\u3002"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "API \u5bc6\u78bc\u7121\u6548"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "API \u5bc6\u9470",
|
||||||
|
"latitude": "\u7def\u5ea6",
|
||||||
|
"longitude": "\u7d93\u5ea6",
|
||||||
|
"show_on_map": "\u65bc\u5730\u5716\u4e0a\u986f\u793a\u76e3\u63a7\u4f4d\u7f6e\u3002"
|
||||||
|
},
|
||||||
|
"description": "\u4f9d\u5730\u7406\u4f4d\u7f6e\u76e3\u63a7\u7a7a\u6c23\u54c1\u8cea\u3002",
|
||||||
|
"title": "\u8a2d\u5b9a AirVisual"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "AirVisual"
|
||||||
|
}
|
||||||
|
}
|
@@ -1 +1,215 @@
|
|||||||
"""The airvisual component."""
|
"""The airvisual component."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyairvisual import Client
|
||||||
|
from pyairvisual.errors import AirVisualError, InvalidKeyError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_API_KEY,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
CONF_STATE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_CITY,
|
||||||
|
CONF_COUNTRY,
|
||||||
|
CONF_GEOGRAPHIES,
|
||||||
|
DATA_CLIENT,
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DOMAIN,
|
||||||
|
TOPIC_UPDATE,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DATA_LISTENER = "listener"
|
||||||
|
|
||||||
|
DEFAULT_OPTIONS = {CONF_SHOW_ON_MAP: True}
|
||||||
|
|
||||||
|
CONF_NODE_ID = "node_id"
|
||||||
|
|
||||||
|
GEOGRAPHY_COORDINATES_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_LATITUDE): cv.latitude,
|
||||||
|
vol.Required(CONF_LONGITUDE): cv.longitude,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
GEOGRAPHY_PLACE_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_CITY): cv.string,
|
||||||
|
vol.Required(CONF_STATE): cv.string,
|
||||||
|
vol.Required(CONF_COUNTRY): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CLOUD_API_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): cv.string,
|
||||||
|
vol.Optional(CONF_GEOGRAPHIES, default=[]): vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[vol.Any(GEOGRAPHY_COORDINATES_SCHEMA, GEOGRAPHY_PLACE_SCHEMA)],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema({DOMAIN: CLOUD_API_SCHEMA}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_get_geography_id(geography_dict):
|
||||||
|
"""Generate a unique ID from a geography dict."""
|
||||||
|
if CONF_CITY in geography_dict:
|
||||||
|
return ",".join(
|
||||||
|
(
|
||||||
|
geography_dict[CONF_CITY],
|
||||||
|
geography_dict[CONF_STATE],
|
||||||
|
geography_dict[CONF_COUNTRY],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return ",".join(
|
||||||
|
(str(geography_dict[CONF_LATITUDE]), str(geography_dict[CONF_LONGITUDE]))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up the AirVisual component."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
|
hass.data[DOMAIN][DATA_CLIENT] = {}
|
||||||
|
hass.data[DOMAIN][DATA_LISTENER] = {}
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
|
conf = config[DOMAIN]
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up AirVisual as config entry."""
|
||||||
|
entry_updates = {}
|
||||||
|
if not config_entry.unique_id:
|
||||||
|
# If the config entry doesn't already have a unique ID, set one:
|
||||||
|
entry_updates["unique_id"] = config_entry.data[CONF_API_KEY]
|
||||||
|
if not config_entry.options:
|
||||||
|
# If the config entry doesn't already have any options set, set defaults:
|
||||||
|
entry_updates["options"] = DEFAULT_OPTIONS
|
||||||
|
|
||||||
|
if entry_updates:
|
||||||
|
hass.config_entries.async_update_entry(config_entry, **entry_updates)
|
||||||
|
|
||||||
|
websession = aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = AirVisualData(
|
||||||
|
hass, Client(websession, api_key=config_entry.data[CONF_API_KEY]), config_entry
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update()
|
||||||
|
except InvalidKeyError:
|
||||||
|
_LOGGER.error("Invalid API key provided")
|
||||||
|
raise ConfigEntryNotReady
|
||||||
|
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def refresh(event_time):
|
||||||
|
"""Refresh data from AirVisual."""
|
||||||
|
await hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id].async_update()
|
||||||
|
|
||||||
|
hass.data[DOMAIN][DATA_LISTENER][config_entry.entry_id] = async_track_time_interval(
|
||||||
|
hass, refresh, DEFAULT_SCAN_INTERVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
config_entry.add_update_listener(async_update_options)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload an AirVisual config entry."""
|
||||||
|
hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id)
|
||||||
|
|
||||||
|
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id)
|
||||||
|
remove_listener()
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_unload(config_entry, "sensor")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_update_options(hass, config_entry):
|
||||||
|
"""Handle an options update."""
|
||||||
|
airvisual = hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id]
|
||||||
|
airvisual.async_update_options(config_entry.options)
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualData:
|
||||||
|
"""Define a class to manage data from the AirVisual cloud API."""
|
||||||
|
|
||||||
|
def __init__(self, hass, client, config_entry):
|
||||||
|
"""Initialize."""
|
||||||
|
self._client = client
|
||||||
|
self._hass = hass
|
||||||
|
self.data = {}
|
||||||
|
self.options = config_entry.options
|
||||||
|
|
||||||
|
self.geographies = {
|
||||||
|
async_get_geography_id(geography): geography
|
||||||
|
for geography in config_entry.data[CONF_GEOGRAPHIES]
|
||||||
|
}
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Get new data for all locations from the AirVisual cloud API."""
|
||||||
|
tasks = []
|
||||||
|
|
||||||
|
for geography in self.geographies.values():
|
||||||
|
if CONF_CITY in geography:
|
||||||
|
tasks.append(
|
||||||
|
self._client.api.city(
|
||||||
|
geography[CONF_CITY],
|
||||||
|
geography[CONF_STATE],
|
||||||
|
geography[CONF_COUNTRY],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
tasks.append(
|
||||||
|
self._client.api.nearest_city(
|
||||||
|
geography[CONF_LATITUDE], geography[CONF_LONGITUDE],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
for geography_id, result in zip(self.geographies, results):
|
||||||
|
if isinstance(result, AirVisualError):
|
||||||
|
_LOGGER.error("Error while retrieving data: %s", result)
|
||||||
|
self.data[geography_id] = {}
|
||||||
|
continue
|
||||||
|
self.data[geography_id] = result
|
||||||
|
|
||||||
|
_LOGGER.debug("Received new data")
|
||||||
|
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_update_options(self, options):
|
||||||
|
"""Update the data manager's options."""
|
||||||
|
self.options = options
|
||||||
|
async_dispatcher_send(self._hass, TOPIC_UPDATE)
|
||||||
|
123
homeassistant/components/airvisual/config_flow.py
Normal file
123
homeassistant/components/airvisual/config_flow.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
"""Define a config flow manager for AirVisual."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyairvisual import Client
|
||||||
|
from pyairvisual.errors import InvalidKeyError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_API_KEY,
|
||||||
|
CONF_LATITUDE,
|
||||||
|
CONF_LONGITUDE,
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
|
||||||
|
from .const import CONF_GEOGRAPHIES, DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger("homeassistant.components.airvisual")
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle an AirVisual config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cloud_api_schema(self):
|
||||||
|
"""Return the data schema for the cloud API."""
|
||||||
|
return vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_API_KEY): str,
|
||||||
|
vol.Required(
|
||||||
|
CONF_LATITUDE, default=self.hass.config.latitude
|
||||||
|
): cv.latitude,
|
||||||
|
vol.Required(
|
||||||
|
CONF_LONGITUDE, default=self.hass.config.longitude
|
||||||
|
): cv.longitude,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_set_unique_id(self, unique_id):
|
||||||
|
"""Set the unique ID of the config flow and abort if it already exists."""
|
||||||
|
await self.async_set_unique_id(unique_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def _show_form(self, errors=None):
|
||||||
|
"""Show the form to the user."""
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=self.cloud_api_schema, errors=errors or {},
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Define the config flow to handle options."""
|
||||||
|
return AirVisualOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a config entry from configuration.yaml."""
|
||||||
|
return await self.async_step_user(import_config)
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the start of the config flow."""
|
||||||
|
if not user_input:
|
||||||
|
return await self._show_form()
|
||||||
|
|
||||||
|
await self._async_set_unique_id(user_input[CONF_API_KEY])
|
||||||
|
|
||||||
|
websession = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
client = Client(websession, api_key=user_input[CONF_API_KEY])
|
||||||
|
|
||||||
|
try:
|
||||||
|
await client.api.nearest_city()
|
||||||
|
except InvalidKeyError:
|
||||||
|
return await self._show_form(errors={CONF_API_KEY: "invalid_api_key"})
|
||||||
|
|
||||||
|
data = {CONF_API_KEY: user_input[CONF_API_KEY]}
|
||||||
|
if user_input.get(CONF_GEOGRAPHIES):
|
||||||
|
data[CONF_GEOGRAPHIES] = user_input[CONF_GEOGRAPHIES]
|
||||||
|
else:
|
||||||
|
data[CONF_GEOGRAPHIES] = [
|
||||||
|
{
|
||||||
|
CONF_LATITUDE: user_input.get(
|
||||||
|
CONF_LATITUDE, self.hass.config.latitude
|
||||||
|
),
|
||||||
|
CONF_LONGITUDE: user_input.get(
|
||||||
|
CONF_LONGITUDE, self.hass.config.longitude
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=f"Cloud API (API key: {user_input[CONF_API_KEY][:4]}...)", data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handle an AirVisual options flow."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry):
|
||||||
|
"""Initialize."""
|
||||||
|
self.config_entry = config_entry
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the options."""
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(title="", data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="init",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_SHOW_ON_MAP,
|
||||||
|
default=self.config_entry.options.get(CONF_SHOW_ON_MAP),
|
||||||
|
): bool
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
14
homeassistant/components/airvisual/const.py
Normal file
14
homeassistant/components/airvisual/const.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""Define AirVisual constants."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
DOMAIN = "airvisual"
|
||||||
|
|
||||||
|
CONF_CITY = "city"
|
||||||
|
CONF_COUNTRY = "country"
|
||||||
|
CONF_GEOGRAPHIES = "geographies"
|
||||||
|
|
||||||
|
DATA_CLIENT = "client"
|
||||||
|
|
||||||
|
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
||||||
|
|
||||||
|
TOPIC_UPDATE = f"{DOMAIN}_update"
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "airvisual",
|
"domain": "airvisual",
|
||||||
"name": "AirVisual",
|
"name": "AirVisual",
|
||||||
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||||
"requirements": ["pyairvisual==3.0.1"],
|
"requirements": ["pyairvisual==3.0.1"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
|
@@ -1,27 +1,24 @@
|
|||||||
"""Support for AirVisual air quality sensors."""
|
"""Support for AirVisual air quality sensors."""
|
||||||
from datetime import timedelta
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from pyairvisual import Client
|
|
||||||
from pyairvisual.errors import AirVisualError
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_LATITUDE,
|
ATTR_LATITUDE,
|
||||||
ATTR_LONGITUDE,
|
ATTR_LONGITUDE,
|
||||||
CONF_API_KEY,
|
ATTR_STATE,
|
||||||
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
CONF_MONITORED_CONDITIONS,
|
|
||||||
CONF_SCAN_INTERVAL,
|
|
||||||
CONF_SHOW_ON_MAP,
|
CONF_SHOW_ON_MAP,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import Throttle
|
|
||||||
|
from .const import CONF_CITY, CONF_COUNTRY, DATA_CLIENT, DOMAIN, TOPIC_UPDATE
|
||||||
|
|
||||||
_LOGGER = getLogger(__name__)
|
_LOGGER = getLogger(__name__)
|
||||||
|
|
||||||
@@ -31,23 +28,19 @@ ATTR_POLLUTANT_SYMBOL = "pollutant_symbol"
|
|||||||
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
ATTR_POLLUTANT_UNIT = "pollutant_unit"
|
||||||
ATTR_REGION = "region"
|
ATTR_REGION = "region"
|
||||||
|
|
||||||
CONF_CITY = "city"
|
|
||||||
CONF_COUNTRY = "country"
|
|
||||||
|
|
||||||
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
||||||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=10)
|
|
||||||
|
|
||||||
MASS_PARTS_PER_MILLION = "ppm"
|
MASS_PARTS_PER_MILLION = "ppm"
|
||||||
MASS_PARTS_PER_BILLION = "ppb"
|
MASS_PARTS_PER_BILLION = "ppb"
|
||||||
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
|
VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3"
|
||||||
|
|
||||||
SENSOR_TYPE_LEVEL = "air_pollution_level"
|
SENSOR_KIND_LEVEL = "air_pollution_level"
|
||||||
SENSOR_TYPE_AQI = "air_quality_index"
|
SENSOR_KIND_AQI = "air_quality_index"
|
||||||
SENSOR_TYPE_POLLUTANT = "main_pollutant"
|
SENSOR_KIND_POLLUTANT = "main_pollutant"
|
||||||
SENSORS = [
|
SENSORS = [
|
||||||
(SENSOR_TYPE_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
|
||||||
(SENSOR_TYPE_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
|
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
|
||||||
(SENSOR_TYPE_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
|
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
|
||||||
]
|
]
|
||||||
|
|
||||||
POLLUTANT_LEVEL_MAPPING = [
|
POLLUTANT_LEVEL_MAPPING = [
|
||||||
@@ -70,112 +63,68 @@ POLLUTANT_LEVEL_MAPPING = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
POLLUTANT_MAPPING = {
|
POLLUTANT_MAPPING = {
|
||||||
"co": {"label": "Carbon Monoxide", "unit": MASS_PARTS_PER_MILLION},
|
"co": {"label": "Carbon Monoxide", "unit": CONCENTRATION_PARTS_PER_MILLION},
|
||||||
"n2": {"label": "Nitrogen Dioxide", "unit": MASS_PARTS_PER_BILLION},
|
"n2": {"label": "Nitrogen Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION},
|
||||||
"o3": {"label": "Ozone", "unit": MASS_PARTS_PER_BILLION},
|
"o3": {"label": "Ozone", "unit": CONCENTRATION_PARTS_PER_BILLION},
|
||||||
"p1": {"label": "PM10", "unit": VOLUME_MICROGRAMS_PER_CUBIC_METER},
|
"p1": {"label": "PM10", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
"p2": {"label": "PM2.5", "unit": VOLUME_MICROGRAMS_PER_CUBIC_METER},
|
"p2": {"label": "PM2.5", "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
|
||||||
"s2": {"label": "Sulfur Dioxide", "unit": MASS_PARTS_PER_BILLION},
|
"s2": {"label": "Sulfur Dioxide", "unit": CONCENTRATION_PARTS_PER_BILLION},
|
||||||
}
|
}
|
||||||
|
|
||||||
SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
vol.Required(CONF_API_KEY): cv.string,
|
"""Set up AirVisual sensors based on a config entry."""
|
||||||
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_LOCALES)): vol.All(
|
airvisual = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
|
||||||
cv.ensure_list, [vol.In(SENSOR_LOCALES)]
|
|
||||||
),
|
async_add_entities(
|
||||||
vol.Inclusive(CONF_CITY, "city"): cv.string,
|
[
|
||||||
vol.Inclusive(CONF_COUNTRY, "city"): cv.string,
|
AirVisualSensor(airvisual, kind, name, icon, unit, locale, geography_id)
|
||||||
vol.Inclusive(CONF_LATITUDE, "coords"): cv.latitude,
|
for geography_id in airvisual.data
|
||||||
vol.Inclusive(CONF_LONGITUDE, "coords"): cv.longitude,
|
for locale in SENSOR_LOCALES
|
||||||
vol.Optional(CONF_SHOW_ON_MAP, default=True): cv.boolean,
|
for kind, name, icon, unit in SENSORS
|
||||||
vol.Inclusive(CONF_STATE, "city"): cv.string,
|
],
|
||||||
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period,
|
True,
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
|
||||||
"""Configure the platform and add the sensors."""
|
|
||||||
|
|
||||||
city = config.get(CONF_CITY)
|
|
||||||
state = config.get(CONF_STATE)
|
|
||||||
country = config.get(CONF_COUNTRY)
|
|
||||||
|
|
||||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
|
|
||||||
websession = aiohttp_client.async_get_clientsession(hass)
|
|
||||||
|
|
||||||
if city and state and country:
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Using city, state, and country: %s, %s, %s", city, state, country
|
|
||||||
)
|
|
||||||
location_id = ",".join((city, state, country))
|
|
||||||
data = AirVisualData(
|
|
||||||
Client(websession, api_key=config[CONF_API_KEY]),
|
|
||||||
city=city,
|
|
||||||
state=state,
|
|
||||||
country=country,
|
|
||||||
show_on_map=config[CONF_SHOW_ON_MAP],
|
|
||||||
scan_interval=config[CONF_SCAN_INTERVAL],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_LOGGER.debug("Using latitude and longitude: %s, %s", latitude, longitude)
|
|
||||||
location_id = ",".join((str(latitude), str(longitude)))
|
|
||||||
data = AirVisualData(
|
|
||||||
Client(websession, api_key=config[CONF_API_KEY]),
|
|
||||||
latitude=latitude,
|
|
||||||
longitude=longitude,
|
|
||||||
show_on_map=config[CONF_SHOW_ON_MAP],
|
|
||||||
scan_interval=config[CONF_SCAN_INTERVAL],
|
|
||||||
)
|
|
||||||
|
|
||||||
await data.async_update()
|
|
||||||
|
|
||||||
sensors = []
|
|
||||||
for locale in config[CONF_MONITORED_CONDITIONS]:
|
|
||||||
for kind, name, icon, unit in SENSORS:
|
|
||||||
sensors.append(
|
|
||||||
AirVisualSensor(data, kind, name, icon, unit, locale, location_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
async_add_entities(sensors, True)
|
|
||||||
|
|
||||||
|
|
||||||
class AirVisualSensor(Entity):
|
class AirVisualSensor(Entity):
|
||||||
"""Define an AirVisual sensor."""
|
"""Define an AirVisual sensor."""
|
||||||
|
|
||||||
def __init__(self, airvisual, kind, name, icon, unit, locale, location_id):
|
def __init__(self, airvisual, kind, name, icon, unit, locale, geography_id):
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
|
self._airvisual = airvisual
|
||||||
|
self._async_unsub_dispatcher_connects = []
|
||||||
|
self._geography_id = geography_id
|
||||||
self._icon = icon
|
self._icon = icon
|
||||||
|
self._kind = kind
|
||||||
self._locale = locale
|
self._locale = locale
|
||||||
self._location_id = location_id
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._state = None
|
self._state = None
|
||||||
self._type = kind
|
|
||||||
self._unit = unit
|
self._unit = unit
|
||||||
self.airvisual = airvisual
|
|
||||||
|
|
||||||
@property
|
self._attrs = {
|
||||||
def device_state_attributes(self):
|
ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION,
|
||||||
"""Return the device state attributes."""
|
ATTR_CITY: airvisual.data[geography_id].get(CONF_CITY),
|
||||||
if self.airvisual.show_on_map:
|
ATTR_STATE: airvisual.data[geography_id].get(CONF_STATE),
|
||||||
self._attrs[ATTR_LATITUDE] = self.airvisual.latitude
|
ATTR_COUNTRY: airvisual.data[geography_id].get(CONF_COUNTRY),
|
||||||
self._attrs[ATTR_LONGITUDE] = self.airvisual.longitude
|
}
|
||||||
else:
|
|
||||||
self._attrs["lati"] = self.airvisual.latitude
|
|
||||||
self._attrs["long"] = self.airvisual.longitude
|
|
||||||
|
|
||||||
return self._attrs
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return bool(self.airvisual.pollution_info)
|
try:
|
||||||
|
return bool(
|
||||||
|
self._airvisual.data[self._geography_id]["current"]["pollution"]
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the device state attributes."""
|
||||||
|
return self._attrs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
@@ -185,7 +134,7 @@ class AirVisualSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name."""
|
"""Return the name."""
|
||||||
return "{0} {1}".format(SENSOR_LOCALES[self._locale], self._name)
|
return f"{SENSOR_LOCALES[self._locale]} {self._name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@@ -195,22 +144,33 @@ class AirVisualSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
"""Return a unique, Home Assistant friendly identifier for this entity."""
|
||||||
return f"{self._location_id}_{self._locale}_{self._type}"
|
return f"{self._geography_id}_{self._locale}_{self._kind}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit the value is expressed in."""
|
"""Return the unit the value is expressed in."""
|
||||||
return self._unit
|
return self._unit
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
"""Register callbacks."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def update():
|
||||||
|
"""Update the state."""
|
||||||
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
|
self._async_unsub_dispatcher_connects.append(
|
||||||
|
async_dispatcher_connect(self.hass, TOPIC_UPDATE, update)
|
||||||
|
)
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Update the sensor."""
|
"""Update the sensor."""
|
||||||
await self.airvisual.async_update()
|
try:
|
||||||
data = self.airvisual.pollution_info
|
data = self._airvisual.data[self._geography_id]["current"]["pollution"]
|
||||||
|
except KeyError:
|
||||||
if not data:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._type == SENSOR_TYPE_LEVEL:
|
if self._kind == SENSOR_KIND_LEVEL:
|
||||||
aqi = data[f"aqi{self._locale}"]
|
aqi = data[f"aqi{self._locale}"]
|
||||||
[level] = [
|
[level] = [
|
||||||
i
|
i
|
||||||
@@ -219,9 +179,9 @@ class AirVisualSensor(Entity):
|
|||||||
]
|
]
|
||||||
self._state = level["label"]
|
self._state = level["label"]
|
||||||
self._icon = level["icon"]
|
self._icon = level["icon"]
|
||||||
elif self._type == SENSOR_TYPE_AQI:
|
elif self._kind == SENSOR_KIND_AQI:
|
||||||
self._state = data[f"aqi{self._locale}"]
|
self._state = data[f"aqi{self._locale}"]
|
||||||
elif self._type == SENSOR_TYPE_POLLUTANT:
|
elif self._kind == SENSOR_KIND_POLLUTANT:
|
||||||
symbol = data[f"main{self._locale}"]
|
symbol = data[f"main{self._locale}"]
|
||||||
self._state = POLLUTANT_MAPPING[symbol]["label"]
|
self._state = POLLUTANT_MAPPING[symbol]["label"]
|
||||||
self._attrs.update(
|
self._attrs.update(
|
||||||
@@ -231,43 +191,21 @@ class AirVisualSensor(Entity):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
geography = self._airvisual.geographies[self._geography_id]
|
||||||
class AirVisualData:
|
if CONF_LATITUDE in geography:
|
||||||
"""Define an object to hold sensor data."""
|
if self._airvisual.options[CONF_SHOW_ON_MAP]:
|
||||||
|
self._attrs[ATTR_LATITUDE] = geography[CONF_LATITUDE]
|
||||||
def __init__(self, client, **kwargs):
|
self._attrs[ATTR_LONGITUDE] = geography[CONF_LONGITUDE]
|
||||||
"""Initialize."""
|
self._attrs.pop("lati", None)
|
||||||
self._client = client
|
self._attrs.pop("long", None)
|
||||||
self.city = kwargs.get(CONF_CITY)
|
|
||||||
self.country = kwargs.get(CONF_COUNTRY)
|
|
||||||
self.latitude = kwargs.get(CONF_LATITUDE)
|
|
||||||
self.longitude = kwargs.get(CONF_LONGITUDE)
|
|
||||||
self.pollution_info = {}
|
|
||||||
self.show_on_map = kwargs.get(CONF_SHOW_ON_MAP)
|
|
||||||
self.state = kwargs.get(CONF_STATE)
|
|
||||||
|
|
||||||
self.async_update = Throttle(kwargs[CONF_SCAN_INTERVAL])(self._async_update)
|
|
||||||
|
|
||||||
async def _async_update(self):
|
|
||||||
"""Update AirVisual data."""
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.city and self.state and self.country:
|
|
||||||
resp = await self._client.api.city(self.city, self.state, self.country)
|
|
||||||
self.longitude, self.latitude = resp["location"]["coordinates"]
|
|
||||||
else:
|
else:
|
||||||
resp = await self._client.api.nearest_city(
|
self._attrs["lati"] = geography[CONF_LATITUDE]
|
||||||
self.latitude, self.longitude
|
self._attrs["long"] = geography[CONF_LONGITUDE]
|
||||||
)
|
self._attrs.pop(ATTR_LATITUDE, None)
|
||||||
|
self._attrs.pop(ATTR_LONGITUDE, None)
|
||||||
|
|
||||||
_LOGGER.debug("New data retrieved: %s", resp)
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
"""Disconnect dispatcher listener when removed."""
|
||||||
self.pollution_info = resp["current"]["pollution"]
|
for cancel in self._async_unsub_dispatcher_connects:
|
||||||
except (KeyError, AirVisualError) as err:
|
cancel()
|
||||||
if self.city and self.state and self.country:
|
self._async_unsub_dispatcher_connects = []
|
||||||
location = (self.city, self.state, self.country)
|
|
||||||
else:
|
|
||||||
location = (self.latitude, self.longitude)
|
|
||||||
|
|
||||||
_LOGGER.error("Can't retrieve data for location: %s (%s)", location, err)
|
|
||||||
self.pollution_info = {}
|
|
||||||
|
33
homeassistant/components/airvisual/strings.json
Normal file
33
homeassistant/components/airvisual/strings.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "AirVisual",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Configure AirVisual",
|
||||||
|
"description": "Monitor air quality in a geographical location.",
|
||||||
|
"data": {
|
||||||
|
"api_key": "API Key",
|
||||||
|
"latitude": "Latitude",
|
||||||
|
"longitude": "Longitude"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_api_key": "Invalid API key"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This API key is already in use."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Configure AirVisual",
|
||||||
|
"description": "Set various options for the AirVisual integration.",
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -53,9 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
except (TypeError, KeyError, NameError, ValueError) as ex:
|
except (TypeError, KeyError, NameError, ValueError) as ex:
|
||||||
_LOGGER.error("%s", ex)
|
_LOGGER.error("%s", ex)
|
||||||
hass.components.persistent_notification.create(
|
hass.components.persistent_notification.create(
|
||||||
"Error: {}<br />"
|
"Error: {ex}<br />You will need to restart hass after fixing.",
|
||||||
"You will need to restart hass after fixing."
|
|
||||||
"".format(ex),
|
|
||||||
title=NOTIFICATION_TITLE,
|
title=NOTIFICATION_TITLE,
|
||||||
notification_id=NOTIFICATION_ID,
|
notification_id=NOTIFICATION_ID,
|
||||||
)
|
)
|
||||||
|
@@ -177,7 +177,7 @@ class AlarmDecoderAlarmPanel(AlarmControlPanel):
|
|||||||
def alarm_arm_night(self, code=None):
|
def alarm_arm_night(self, code=None):
|
||||||
"""Send arm night command."""
|
"""Send arm night command."""
|
||||||
if code:
|
if code:
|
||||||
self.hass.data[DATA_AD].send(f"{code!s}33")
|
self.hass.data[DATA_AD].send(f"{code!s}7")
|
||||||
|
|
||||||
def alarm_toggle_chime(self, code=None):
|
def alarm_toggle_chime(self, code=None):
|
||||||
"""Send toggle chime command."""
|
"""Send toggle chime command."""
|
||||||
|
@@ -31,7 +31,6 @@ from homeassistant.util.dt import now
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "alert"
|
DOMAIN = "alert"
|
||||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
|
||||||
|
|
||||||
CONF_CAN_ACK = "can_acknowledge"
|
CONF_CAN_ACK = "can_acknowledge"
|
||||||
CONF_NOTIFIERS = "notifiers"
|
CONF_NOTIFIERS = "notifiers"
|
||||||
@@ -200,7 +199,7 @@ class Alert(ToggleEntity):
|
|||||||
self._ack = False
|
self._ack = False
|
||||||
self._cancel = None
|
self._cancel = None
|
||||||
self._send_done_message = False
|
self._send_done_message = False
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(entity_id)
|
self.entity_id = f"{DOMAIN}.{entity_id}"
|
||||||
|
|
||||||
event.async_track_state_change(
|
event.async_track_state_change(
|
||||||
hass, watched_entity_id, self.watched_entity_change
|
hass, watched_entity_id, self.watched_entity_change
|
||||||
|
@@ -4,6 +4,7 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv, entityfilter
|
from homeassistant.helpers import config_validation as cv, entityfilter
|
||||||
|
|
||||||
from . import flash_briefings, intent, smart_home_http
|
from . import flash_briefings, intent, smart_home_http
|
||||||
@@ -23,6 +24,7 @@ from .const import (
|
|||||||
CONF_TITLE,
|
CONF_TITLE,
|
||||||
CONF_UID,
|
CONF_UID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
EVENT_ALEXA_SMART_HOME,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -80,7 +82,37 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass, config):
|
||||||
"""Activate the Alexa component."""
|
"""Activate the Alexa component."""
|
||||||
config = config.get(DOMAIN, {})
|
|
||||||
|
@callback
|
||||||
|
def async_describe_logbook_event(event):
|
||||||
|
"""Describe a logbook event."""
|
||||||
|
data = event.data
|
||||||
|
entity_id = data["request"].get("entity_id")
|
||||||
|
|
||||||
|
if entity_id:
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
name = state.name if state else entity_id
|
||||||
|
message = f"send command {data['request']['namespace']}/{data['request']['name']} for {name}"
|
||||||
|
else:
|
||||||
|
message = (
|
||||||
|
f"send command {data['request']['namespace']}/{data['request']['name']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": "Amazon Alexa",
|
||||||
|
"message": message,
|
||||||
|
"entity_id": entity_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.components.logbook.async_describe_event(
|
||||||
|
DOMAIN, EVENT_ALEXA_SMART_HOME, async_describe_logbook_event
|
||||||
|
)
|
||||||
|
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
|
config = config[DOMAIN]
|
||||||
|
|
||||||
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)
|
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)
|
||||||
|
|
||||||
intent.async_setup(hass)
|
intent.async_setup(hass)
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
"""Alexa capabilities."""
|
"""Alexa capabilities."""
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
|
|
||||||
from homeassistant.components import (
|
from homeassistant.components import (
|
||||||
cover,
|
cover,
|
||||||
@@ -8,6 +7,7 @@ from homeassistant.components import (
|
|||||||
image_processing,
|
image_processing,
|
||||||
input_number,
|
input_number,
|
||||||
light,
|
light,
|
||||||
|
timer,
|
||||||
vacuum,
|
vacuum,
|
||||||
)
|
)
|
||||||
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
|
from homeassistant.components.alarm_control_panel import ATTR_CODE_FORMAT, FORMAT_NUMBER
|
||||||
@@ -26,6 +26,7 @@ from homeassistant.const import (
|
|||||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||||
STATE_ALARM_ARMED_HOME,
|
STATE_ALARM_ARMED_HOME,
|
||||||
STATE_ALARM_ARMED_NIGHT,
|
STATE_ALARM_ARMED_NIGHT,
|
||||||
|
STATE_IDLE,
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
@@ -227,7 +228,6 @@ class AlexaCapability:
|
|||||||
"""Return properties serialized for an API response."""
|
"""Return properties serialized for an API response."""
|
||||||
for prop in self.properties_supported():
|
for prop in self.properties_supported():
|
||||||
prop_name = prop["name"]
|
prop_name = prop["name"]
|
||||||
# pylint: disable=assignment-from-no-return
|
|
||||||
prop_value = self.get_property(prop_name)
|
prop_value = self.get_property(prop_name)
|
||||||
if prop_value is not None:
|
if prop_value is not None:
|
||||||
result = {
|
result = {
|
||||||
@@ -365,6 +365,10 @@ class AlexaPowerController(AlexaCapability):
|
|||||||
|
|
||||||
if self.entity.domain == climate.DOMAIN:
|
if self.entity.domain == climate.DOMAIN:
|
||||||
is_on = self.entity.state != climate.HVAC_MODE_OFF
|
is_on = self.entity.state != climate.HVAC_MODE_OFF
|
||||||
|
elif self.entity.domain == vacuum.DOMAIN:
|
||||||
|
is_on = self.entity.state == vacuum.STATE_CLEANING
|
||||||
|
elif self.entity.domain == timer.DOMAIN:
|
||||||
|
is_on = self.entity.state != STATE_IDLE
|
||||||
|
|
||||||
else:
|
else:
|
||||||
is_on = self.entity.state != STATE_OFF
|
is_on = self.entity.state != STATE_OFF
|
||||||
@@ -670,11 +674,8 @@ class AlexaSpeaker(AlexaCapability):
|
|||||||
current_level = self.entity.attributes.get(
|
current_level = self.entity.attributes.get(
|
||||||
media_player.ATTR_MEDIA_VOLUME_LEVEL
|
media_player.ATTR_MEDIA_VOLUME_LEVEL
|
||||||
)
|
)
|
||||||
try:
|
if current_level is not None:
|
||||||
current = math.floor(int(current_level * 100))
|
return round(float(current_level) * 100)
|
||||||
except ZeroDivisionError:
|
|
||||||
current = 0
|
|
||||||
return current
|
|
||||||
|
|
||||||
if name == "muted":
|
if name == "muted":
|
||||||
return bool(
|
return bool(
|
||||||
|
@@ -53,7 +53,7 @@ class AbstractConfig(ABC):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await self._unsub_proactive_report
|
await self._unsub_proactive_report
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception:
|
||||||
self._unsub_proactive_report = None
|
self._unsub_proactive_report = None
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ from homeassistant.components.climate import const as climate
|
|||||||
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
|
||||||
|
|
||||||
DOMAIN = "alexa"
|
DOMAIN = "alexa"
|
||||||
|
EVENT_ALEXA_SMART_HOME = "alexa_smart_home"
|
||||||
|
|
||||||
# Flash briefing constants
|
# Flash briefing constants
|
||||||
CONF_UID = "uid"
|
CONF_UID = "uid"
|
||||||
|
@@ -400,7 +400,10 @@ class CoverCapabilities(AlexaEntity):
|
|||||||
|
|
||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
|
device_class = self.entity.attributes.get(ATTR_DEVICE_CLASS)
|
||||||
|
if device_class != cover.DEVICE_CLASS_GARAGE:
|
||||||
yield AlexaPowerController(self.entity)
|
yield AlexaPowerController(self.entity)
|
||||||
|
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if supported & cover.SUPPORT_SET_POSITION:
|
if supported & cover.SUPPORT_SET_POSITION:
|
||||||
yield AlexaRangeController(
|
yield AlexaRangeController(
|
||||||
@@ -724,6 +727,7 @@ class TimerCapabilities(AlexaEntity):
|
|||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
yield AlexaTimeHoldController(self.entity, allow_remote_resume=True)
|
yield AlexaTimeHoldController(self.entity, allow_remote_resume=True)
|
||||||
|
yield AlexaPowerController(self.entity)
|
||||||
yield Alexa(self.entity)
|
yield Alexa(self.entity)
|
||||||
|
|
||||||
|
|
||||||
@@ -738,8 +742,11 @@ class VacuumCapabilities(AlexaEntity):
|
|||||||
def interfaces(self):
|
def interfaces(self):
|
||||||
"""Yield the supported interfaces."""
|
"""Yield the supported interfaces."""
|
||||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
if (supported & vacuum.SUPPORT_TURN_ON) and (
|
if (
|
||||||
supported & vacuum.SUPPORT_TURN_OFF
|
(supported & vacuum.SUPPORT_TURN_ON) or (supported & vacuum.SUPPORT_START)
|
||||||
|
) and (
|
||||||
|
(supported & vacuum.SUPPORT_TURN_OFF)
|
||||||
|
or (supported & vacuum.SUPPORT_RETURN_HOME)
|
||||||
):
|
):
|
||||||
yield AlexaPowerController(self.entity)
|
yield AlexaPowerController(self.entity)
|
||||||
|
|
||||||
|
@@ -121,6 +121,12 @@ async def async_api_turn_on(hass, config, directive, context):
|
|||||||
service = SERVICE_TURN_ON
|
service = SERVICE_TURN_ON
|
||||||
if domain == cover.DOMAIN:
|
if domain == cover.DOMAIN:
|
||||||
service = cover.SERVICE_OPEN_COVER
|
service = cover.SERVICE_OPEN_COVER
|
||||||
|
elif domain == vacuum.DOMAIN:
|
||||||
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
if not supported & vacuum.SUPPORT_TURN_ON and supported & vacuum.SUPPORT_START:
|
||||||
|
service = vacuum.SERVICE_START
|
||||||
|
elif domain == timer.DOMAIN:
|
||||||
|
service = timer.SERVICE_START
|
||||||
elif domain == media_player.DOMAIN:
|
elif domain == media_player.DOMAIN:
|
||||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||||
@@ -149,6 +155,15 @@ async def async_api_turn_off(hass, config, directive, context):
|
|||||||
service = SERVICE_TURN_OFF
|
service = SERVICE_TURN_OFF
|
||||||
if entity.domain == cover.DOMAIN:
|
if entity.domain == cover.DOMAIN:
|
||||||
service = cover.SERVICE_CLOSE_COVER
|
service = cover.SERVICE_CLOSE_COVER
|
||||||
|
elif domain == vacuum.DOMAIN:
|
||||||
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
if (
|
||||||
|
not supported & vacuum.SUPPORT_TURN_OFF
|
||||||
|
and supported & vacuum.SUPPORT_RETURN_HOME
|
||||||
|
):
|
||||||
|
service = vacuum.SERVICE_RETURN_TO_BASE
|
||||||
|
elif domain == timer.DOMAIN:
|
||||||
|
service = timer.SERVICE_CANCEL
|
||||||
elif domain == media_player.DOMAIN:
|
elif domain == media_player.DOMAIN:
|
||||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
power_features = media_player.SUPPORT_TURN_ON | media_player.SUPPORT_TURN_OFF
|
||||||
@@ -478,8 +493,8 @@ async def async_api_select_input(hass, config, directive, context):
|
|||||||
media_input = source
|
media_input = source
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
msg = "failed to map input {} to a media source on {}".format(
|
msg = (
|
||||||
media_input, entity.entity_id
|
f"failed to map input {media_input} to a media source on {entity.entity_id}"
|
||||||
)
|
)
|
||||||
raise AlexaInvalidValueError(msg)
|
raise AlexaInvalidValueError(msg)
|
||||||
|
|
||||||
@@ -1225,7 +1240,7 @@ async def async_api_adjust_range(hass, config, directive, context):
|
|||||||
service = SERVICE_SET_COVER_POSITION
|
service = SERVICE_SET_COVER_POSITION
|
||||||
current = entity.attributes.get(cover.ATTR_POSITION)
|
current = entity.attributes.get(cover.ATTR_POSITION)
|
||||||
if not current:
|
if not current:
|
||||||
msg = "Unable to determine {} current position".format(entity.entity_id)
|
msg = f"Unable to determine {entity.entity_id} current position"
|
||||||
raise AlexaInvalidValueError(msg)
|
raise AlexaInvalidValueError(msg)
|
||||||
position = response_value = min(100, max(0, range_delta + current))
|
position = response_value = min(100, max(0, range_delta + current))
|
||||||
if position == 100:
|
if position == 100:
|
||||||
@@ -1241,9 +1256,7 @@ async def async_api_adjust_range(hass, config, directive, context):
|
|||||||
service = SERVICE_SET_COVER_TILT_POSITION
|
service = SERVICE_SET_COVER_TILT_POSITION
|
||||||
current = entity.attributes.get(cover.ATTR_TILT_POSITION)
|
current = entity.attributes.get(cover.ATTR_TILT_POSITION)
|
||||||
if not current:
|
if not current:
|
||||||
msg = "Unable to determine {} current tilt position".format(
|
msg = f"Unable to determine {entity.entity_id} current tilt position"
|
||||||
entity.entity_id
|
|
||||||
)
|
|
||||||
raise AlexaInvalidValueError(msg)
|
raise AlexaInvalidValueError(msg)
|
||||||
tilt_position = response_value = min(100, max(0, range_delta + current))
|
tilt_position = response_value = min(100, max(0, range_delta + current))
|
||||||
if tilt_position == 100:
|
if tilt_position == 100:
|
||||||
@@ -1439,9 +1452,7 @@ async def async_api_set_eq_mode(hass, config, directive, context):
|
|||||||
if sound_mode_list and mode.lower() in sound_mode_list:
|
if sound_mode_list and mode.lower() in sound_mode_list:
|
||||||
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
|
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
|
||||||
else:
|
else:
|
||||||
msg = "failed to map sound mode {} to a mode on {}".format(
|
msg = f"failed to map sound mode {mode} to a mode on {entity.entity_id}"
|
||||||
mode, entity.entity_id
|
|
||||||
)
|
|
||||||
raise AlexaInvalidValueError(msg)
|
raise AlexaInvalidValueError(msg)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
|
@@ -4,5 +4,6 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
|
"after_dependencies": ["logbook"],
|
||||||
"codeowners": ["@home-assistant/cloud", "@ochlocracy"]
|
"codeowners": ["@home-assistant/cloud", "@ochlocracy"]
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,13 @@ import logging
|
|||||||
|
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
|
|
||||||
from .const import API_DIRECTIVE, API_HEADER
|
from .const import API_DIRECTIVE, API_HEADER, EVENT_ALEXA_SMART_HOME
|
||||||
from .errors import AlexaBridgeUnreachableError, AlexaError
|
from .errors import AlexaBridgeUnreachableError, AlexaError
|
||||||
from .handlers import HANDLERS
|
from .handlers import HANDLERS
|
||||||
from .messages import AlexaDirective
|
from .messages import AlexaDirective
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
EVENT_ALEXA_SMART_HOME = "alexa_smart_home"
|
|
||||||
|
|
||||||
|
|
||||||
async def async_handle_message(hass, config, request, context=None, enabled=True):
|
async def async_handle_message(hass, config, request, context=None, enabled=True):
|
||||||
"""Handle incoming API messages.
|
"""Handle incoming API messages.
|
||||||
|
@@ -26,6 +26,9 @@ async def async_enable_proactive_mode(hass, smart_home_config):
|
|||||||
await smart_home_config.async_get_access_token()
|
await smart_home_config.async_get_access_token()
|
||||||
|
|
||||||
async def async_entity_state_listener(changed_entity, old_state, new_state):
|
async def async_entity_state_listener(changed_entity, old_state, new_state):
|
||||||
|
if not hass.is_running:
|
||||||
|
return
|
||||||
|
|
||||||
if not new_state:
|
if not new_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@@ -87,7 +87,6 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
|
|||||||
)
|
)
|
||||||
return self.async_abort(reason="cannot_connect")
|
return self.async_abort(reason="cannot_connect")
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
|
||||||
self.CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
self.CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Aquesta clau d'aplicaci\u00f3 ja est\u00e0 en \u00fas."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada",
|
"identifier_exists": "Clau d'aplicaci\u00f3 i/o clau API ja registrada",
|
||||||
"invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es",
|
"invalid_key": "Clau API i/o clau d'aplicaci\u00f3 inv\u00e0lida/es",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Denne appn\u00f8gle er allerede i brug."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Applikationsn\u00f8gle og/eller API n\u00f8gle er allerede registreret",
|
"identifier_exists": "Applikationsn\u00f8gle og/eller API n\u00f8gle er allerede registreret",
|
||||||
"invalid_key": "Ugyldig API n\u00f8gle og/eller applikationsn\u00f8gle",
|
"invalid_key": "Ugyldig API n\u00f8gle og/eller applikationsn\u00f8gle",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Dieser App-Schl\u00fcssel wird bereits verwendet."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Anwendungsschl\u00fcssel und / oder API-Schl\u00fcssel bereits registriert",
|
"identifier_exists": "Anwendungsschl\u00fcssel und / oder API-Schl\u00fcssel bereits registriert",
|
||||||
"invalid_key": "Ung\u00fcltiger API Key und / oder Anwendungsschl\u00fcssel",
|
"invalid_key": "Ung\u00fcltiger API Key und / oder Anwendungsschl\u00fcssel",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This app key is already in use."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Application Key and/or API Key already registered",
|
"identifier_exists": "Application Key and/or API Key already registered",
|
||||||
"invalid_key": "Invalid API Key and/or Application Key",
|
"invalid_key": "Invalid API Key and/or Application Key",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Esta clave API ya est\u00e1 en uso."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "La clave API y/o la clave de aplicaci\u00f3n ya est\u00e1 registrada",
|
"identifier_exists": "La clave API y/o la clave de aplicaci\u00f3n ya est\u00e1 registrada",
|
||||||
"invalid_key": "Clave API y/o clave de aplicaci\u00f3n no v\u00e1lida",
|
"invalid_key": "Clave API y/o clave de aplicaci\u00f3n no v\u00e1lida",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Questa chiave dell'app \u00e8 gi\u00e0 in uso."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "API Key e/o Application Key gi\u00e0 registrata",
|
"identifier_exists": "API Key e/o Application Key gi\u00e0 registrata",
|
||||||
"invalid_key": "API Key e/o Application Key non valida",
|
"invalid_key": "API Key e/o Application Key non valida",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\uc774 \uc571 \ud0a4\ub294 \uc774\ubbf8 \uc0ac\uc6a9 \uc911\uc785\ub2c8\ub2e4."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
"identifier_exists": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc774\ubbf8 \ub4f1\ub85d\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||||
"invalid_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
"invalid_key": "\uc560\ud50c\ub9ac\ucf00\uc774\uc158 \ud0a4 \ud639\uc740 API \ud0a4\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "D\u00ebsen App Schl\u00ebssel g\u00ebtt scho benotzt"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert",
|
"identifier_exists": "Applikatioun's Schl\u00ebssel an/oder API Schl\u00ebssel ass scho registr\u00e9iert",
|
||||||
"invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel",
|
"invalid_key": "Ong\u00ebltegen API Schl\u00ebssel an/oder Applikatioun's Schl\u00ebssel",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Denne app n\u00f8kkelen er allerede i bruk."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Programn\u00f8kkel og/eller API-n\u00f8kkel er allerede registrert",
|
"identifier_exists": "Programn\u00f8kkel og/eller API-n\u00f8kkel er allerede registrert",
|
||||||
"invalid_key": "Ugyldig API-n\u00f8kkel og/eller programn\u00f8kkel",
|
"invalid_key": "Ugyldig API-n\u00f8kkel og/eller programn\u00f8kkel",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Ten klucz aplikacji jest ju\u017c w u\u017cyciu."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Klucz aplikacji i/lub klucz API ju\u017c jest zarejestrowany.",
|
"identifier_exists": "Klucz aplikacji i/lub klucz API ju\u017c jest zarejestrowany.",
|
||||||
"invalid_key": "Nieprawid\u0142owy klucz API i/lub klucz aplikacji",
|
"invalid_key": "Nieprawid\u0142owy klucz API i/lub klucz aplikacji",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u042d\u0442\u043e\u0442 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f."
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d.",
|
"identifier_exists": "\u041a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 API \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d.",
|
||||||
"invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.",
|
"invalid_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API \u0438/\u0438\u043b\u0438 \u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.",
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "\u6b64\u61c9\u7528\u7a0b\u5f0f\u5bc6\u9470\u5df2\u88ab\u4f7f\u7528\u3002"
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a",
|
"identifier_exists": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u5df2\u8a3b\u518a",
|
||||||
"invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548",
|
"invalid_key": "API \u5bc6\u9470\u53ca/\u6216\u61c9\u7528\u5bc6\u9470\u7121\u6548",
|
||||||
|
@@ -10,8 +10,11 @@ from homeassistant.config_entries import SOURCE_IMPORT
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_LOCATION,
|
ATTR_LOCATION,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_API_KEY,
|
CONF_API_KEY,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
SPEED_MILES_PER_HOUR,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
@@ -23,14 +26,12 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
from .config_flow import configured_instances
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_LAST_DATA,
|
ATTR_LAST_DATA,
|
||||||
ATTR_MONITORED_CONDITIONS,
|
ATTR_MONITORED_CONDITIONS,
|
||||||
CONF_APP_KEY,
|
CONF_APP_KEY,
|
||||||
DATA_CLIENT,
|
DATA_CLIENT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
TOPIC_UPDATE,
|
|
||||||
TYPE_BINARY_SENSOR,
|
TYPE_BINARY_SENSOR,
|
||||||
TYPE_SENSOR,
|
TYPE_SENSOR,
|
||||||
)
|
)
|
||||||
@@ -148,26 +149,26 @@ SENSOR_TYPES = {
|
|||||||
TYPE_BATT8: ("Battery 8", None, TYPE_BINARY_SENSOR, "battery"),
|
TYPE_BATT8: ("Battery 8", None, TYPE_BINARY_SENSOR, "battery"),
|
||||||
TYPE_BATT9: ("Battery 9", None, TYPE_BINARY_SENSOR, "battery"),
|
TYPE_BATT9: ("Battery 9", None, TYPE_BINARY_SENSOR, "battery"),
|
||||||
TYPE_BATTOUT: ("Battery", None, TYPE_BINARY_SENSOR, "battery"),
|
TYPE_BATTOUT: ("Battery", None, TYPE_BINARY_SENSOR, "battery"),
|
||||||
TYPE_CO2: ("co2", "ppm", TYPE_SENSOR, None),
|
TYPE_CO2: ("co2", CONCENTRATION_PARTS_PER_MILLION, TYPE_SENSOR, None),
|
||||||
TYPE_DAILYRAININ: ("Daily Rain", "in", TYPE_SENSOR, None),
|
TYPE_DAILYRAININ: ("Daily Rain", "in", TYPE_SENSOR, None),
|
||||||
TYPE_DEWPOINT: ("Dew Point", "°F", TYPE_SENSOR, "temperature"),
|
TYPE_DEWPOINT: ("Dew Point", "°F", TYPE_SENSOR, "temperature"),
|
||||||
TYPE_EVENTRAININ: ("Event Rain", "in", TYPE_SENSOR, None),
|
TYPE_EVENTRAININ: ("Event Rain", "in", TYPE_SENSOR, None),
|
||||||
TYPE_FEELSLIKE: ("Feels Like", "°F", TYPE_SENSOR, "temperature"),
|
TYPE_FEELSLIKE: ("Feels Like", "°F", TYPE_SENSOR, "temperature"),
|
||||||
TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", TYPE_SENSOR, None),
|
TYPE_HOURLYRAININ: ("Hourly Rain Rate", "in/hr", TYPE_SENSOR, None),
|
||||||
TYPE_HUMIDITY10: ("Humidity 10", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY10: ("Humidity 10", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY1: ("Humidity 1", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY1: ("Humidity 1", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY2: ("Humidity 2", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY2: ("Humidity 2", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY3: ("Humidity 3", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY3: ("Humidity 3", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY4: ("Humidity 4", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY4: ("Humidity 4", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY5: ("Humidity 5", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY5: ("Humidity 5", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY6: ("Humidity 6", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY6: ("Humidity 6", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY7: ("Humidity 7", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY7: ("Humidity 7", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY8: ("Humidity 8", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY8: ("Humidity 8", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY9: ("Humidity 9", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY9: ("Humidity 9", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITY: ("Humidity", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITY: ("Humidity", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_HUMIDITYIN: ("Humidity In", "%", TYPE_SENSOR, "humidity"),
|
TYPE_HUMIDITYIN: ("Humidity In", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"),
|
TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"),
|
||||||
TYPE_MAXDAILYGUST: ("Max Gust", "mph", TYPE_SENSOR, None),
|
TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None),
|
TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None),
|
||||||
TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, "connectivity"),
|
TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, "connectivity"),
|
||||||
TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, "connectivity"),
|
TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, "connectivity"),
|
||||||
@@ -179,16 +180,16 @@ SENSOR_TYPES = {
|
|||||||
TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, "connectivity"),
|
TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, "connectivity"),
|
||||||
TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, "connectivity"),
|
TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, "connectivity"),
|
||||||
TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, "connectivity"),
|
TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, "connectivity"),
|
||||||
TYPE_SOILHUM10: ("Soil Humidity 10", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM10: ("Soil Humidity 10", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM1: ("Soil Humidity 1", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM1: ("Soil Humidity 1", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM2: ("Soil Humidity 2", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM2: ("Soil Humidity 2", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM3: ("Soil Humidity 3", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM3: ("Soil Humidity 3", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM4: ("Soil Humidity 4", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM4: ("Soil Humidity 4", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM5: ("Soil Humidity 5", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM5: ("Soil Humidity 5", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM6: ("Soil Humidity 6", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM6: ("Soil Humidity 6", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM7: ("Soil Humidity 7", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM7: ("Soil Humidity 7", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM8: ("Soil Humidity 8", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM8: ("Soil Humidity 8", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILHUM9: ("Soil Humidity 9", "%", TYPE_SENSOR, "humidity"),
|
TYPE_SOILHUM9: ("Soil Humidity 9", UNIT_PERCENTAGE, TYPE_SENSOR, "humidity"),
|
||||||
TYPE_SOILTEMP10F: ("Soil Temp 10", "°F", TYPE_SENSOR, "temperature"),
|
TYPE_SOILTEMP10F: ("Soil Temp 10", "°F", TYPE_SENSOR, "temperature"),
|
||||||
TYPE_SOILTEMP1F: ("Soil Temp 1", "°F", TYPE_SENSOR, "temperature"),
|
TYPE_SOILTEMP1F: ("Soil Temp 1", "°F", TYPE_SENSOR, "temperature"),
|
||||||
TYPE_SOILTEMP2F: ("Soil Temp 2", "°F", TYPE_SENSOR, "temperature"),
|
TYPE_SOILTEMP2F: ("Soil Temp 2", "°F", TYPE_SENSOR, "temperature"),
|
||||||
@@ -218,12 +219,12 @@ SENSOR_TYPES = {
|
|||||||
TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None),
|
TYPE_WEEKLYRAININ: ("Weekly Rain", "in", TYPE_SENSOR, None),
|
||||||
TYPE_WINDDIR: ("Wind Dir", "°", TYPE_SENSOR, None),
|
TYPE_WINDDIR: ("Wind Dir", "°", TYPE_SENSOR, None),
|
||||||
TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", "°", TYPE_SENSOR, None),
|
TYPE_WINDDIR_AVG10M: ("Wind Dir Avg 10m", "°", TYPE_SENSOR, None),
|
||||||
TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", "mph", TYPE_SENSOR, None),
|
TYPE_WINDDIR_AVG2M: ("Wind Dir Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_WINDGUSTDIR: ("Gust Dir", "°", TYPE_SENSOR, None),
|
TYPE_WINDGUSTDIR: ("Gust Dir", "°", TYPE_SENSOR, None),
|
||||||
TYPE_WINDGUSTMPH: ("Wind Gust", "mph", TYPE_SENSOR, None),
|
TYPE_WINDGUSTMPH: ("Wind Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", "mph", TYPE_SENSOR, None),
|
TYPE_WINDSPDMPH_AVG10M: ("Wind Avg 10m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", "mph", TYPE_SENSOR, None),
|
TYPE_WINDSPDMPH_AVG2M: ("Wind Avg 2m", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_WINDSPEEDMPH: ("Wind Speed", "mph", TYPE_SENSOR, None),
|
TYPE_WINDSPEEDMPH: ("Wind Speed", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None),
|
||||||
TYPE_YEARLYRAININ: ("Yearly Rain", "in", TYPE_SENSOR, None),
|
TYPE_YEARLYRAININ: ("Yearly Rain", "in", TYPE_SENSOR, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +254,6 @@ async def async_setup(hass, config):
|
|||||||
# Store config for use during entry setup:
|
# Store config for use during entry setup:
|
||||||
hass.data[DOMAIN][DATA_CONFIG] = conf
|
hass.data[DOMAIN][DATA_CONFIG] = conf
|
||||||
|
|
||||||
if conf[CONF_APP_KEY] in configured_instances(hass):
|
|
||||||
return True
|
|
||||||
|
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -269,6 +267,11 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
async def async_setup_entry(hass, config_entry):
|
async def async_setup_entry(hass, config_entry):
|
||||||
"""Set up the Ambient PWS as config entry."""
|
"""Set up the Ambient PWS as config entry."""
|
||||||
|
if not config_entry.unique_id:
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
config_entry, unique_id=config_entry.data[CONF_APP_KEY]
|
||||||
|
)
|
||||||
|
|
||||||
session = aiohttp_client.async_get_clientsession(hass)
|
session = aiohttp_client.async_get_clientsession(hass)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -378,7 +381,9 @@ class AmbientStation:
|
|||||||
if data != self.stations[mac_address][ATTR_LAST_DATA]:
|
if data != self.stations[mac_address][ATTR_LAST_DATA]:
|
||||||
_LOGGER.debug("New data received: %s", data)
|
_LOGGER.debug("New data received: %s", data)
|
||||||
self.stations[mac_address][ATTR_LAST_DATA] = data
|
self.stations[mac_address][ATTR_LAST_DATA] = data
|
||||||
async_dispatcher_send(self._hass, TOPIC_UPDATE.format(mac_address))
|
async_dispatcher_send(
|
||||||
|
self._hass, f"ambient_station_data_update_{mac_address}"
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.debug("Resetting watchdog")
|
_LOGGER.debug("Resetting watchdog")
|
||||||
self._watchdog_listener()
|
self._watchdog_listener()
|
||||||
@@ -518,7 +523,7 @@ class AmbientWeatherEntity(Entity):
|
|||||||
self.async_schedule_update_ha_state(True)
|
self.async_schedule_update_ha_state(True)
|
||||||
|
|
||||||
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
|
||||||
self.hass, TOPIC_UPDATE.format(self._mac_address), update
|
self.hass, f"ambient_station_data_update_{self._mac_address}", update
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self):
|
||||||
|
@@ -5,35 +5,29 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_API_KEY
|
from homeassistant.const import CONF_API_KEY
|
||||||
from homeassistant.core import callback
|
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
|
||||||
from .const import CONF_APP_KEY, DOMAIN
|
from .const import CONF_APP_KEY, DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
|
|
||||||
@callback
|
class AmbientStationFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
def configured_instances(hass):
|
|
||||||
"""Return a set of configured Ambient PWS instances."""
|
|
||||||
return set(
|
|
||||||
entry.data[CONF_APP_KEY] for entry in hass.config_entries.async_entries(DOMAIN)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@config_entries.HANDLERS.register(DOMAIN)
|
|
||||||
class AmbientStationFlowHandler(config_entries.ConfigFlow):
|
|
||||||
"""Handle an Ambient PWS config flow."""
|
"""Handle an Ambient PWS config flow."""
|
||||||
|
|
||||||
VERSION = 2
|
VERSION = 2
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
|
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
|
||||||
|
|
||||||
async def _show_form(self, errors=None):
|
def __init__(self):
|
||||||
"""Show the form to the user."""
|
"""Initialize the config flow."""
|
||||||
data_schema = vol.Schema(
|
self.data_schema = vol.Schema(
|
||||||
{vol.Required(CONF_API_KEY): str, vol.Required(CONF_APP_KEY): str}
|
{vol.Required(CONF_API_KEY): str, vol.Required(CONF_APP_KEY): str}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _show_form(self, errors=None):
|
||||||
|
"""Show the form to the user."""
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="user", data_schema=data_schema, errors=errors if errors else {}
|
step_id="user",
|
||||||
|
data_schema=self.data_schema,
|
||||||
|
errors=errors if errors else {},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_import(self, import_config):
|
async def async_step_import(self, import_config):
|
||||||
@@ -42,12 +36,11 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow):
|
|||||||
|
|
||||||
async def async_step_user(self, user_input=None):
|
async def async_step_user(self, user_input=None):
|
||||||
"""Handle the start of the config flow."""
|
"""Handle the start of the config flow."""
|
||||||
|
|
||||||
if not user_input:
|
if not user_input:
|
||||||
return await self._show_form()
|
return await self._show_form()
|
||||||
|
|
||||||
if user_input[CONF_APP_KEY] in configured_instances(self.hass):
|
await self.async_set_unique_id(user_input[CONF_APP_KEY])
|
||||||
return await self._show_form({CONF_APP_KEY: "identifier_exists"})
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
session = aiohttp_client.async_get_clientsession(self.hass)
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
client = Client(user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session)
|
client = Client(user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session)
|
||||||
|
@@ -8,7 +8,5 @@ CONF_APP_KEY = "app_key"
|
|||||||
|
|
||||||
DATA_CLIENT = "data_client"
|
DATA_CLIENT = "data_client"
|
||||||
|
|
||||||
TOPIC_UPDATE = "ambient_station_data_update_{0}"
|
|
||||||
|
|
||||||
TYPE_BINARY_SENSOR = "binary_sensor"
|
TYPE_BINARY_SENSOR = "binary_sensor"
|
||||||
TYPE_SENSOR = "sensor"
|
TYPE_SENSOR = "sensor"
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
"name": "Ambient Weather Station",
|
"name": "Ambient Weather Station",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ambient_station",
|
"documentation": "https://www.home-assistant.io/integrations/ambient_station",
|
||||||
"requirements": ["aioambient==1.0.2"],
|
"requirements": ["aioambient==1.0.4"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@bachya"]
|
"codeowners": ["@bachya"]
|
||||||
}
|
}
|
||||||
|
@@ -83,6 +83,10 @@ class AmbientWeatherSensor(AmbientWeatherEntity):
|
|||||||
w_m2_brightness_val = self._ambient.stations[self._mac_address][
|
w_m2_brightness_val = self._ambient.stations[self._mac_address][
|
||||||
ATTR_LAST_DATA
|
ATTR_LAST_DATA
|
||||||
].get(TYPE_SOLARRADIATION)
|
].get(TYPE_SOLARRADIATION)
|
||||||
|
|
||||||
|
if w_m2_brightness_val is None:
|
||||||
|
self._state = None
|
||||||
|
else:
|
||||||
self._state = round(float(w_m2_brightness_val) / 0.0079)
|
self._state = round(float(w_m2_brightness_val) / 0.0079)
|
||||||
else:
|
else:
|
||||||
self._state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
|
self._state = self._ambient.stations[self._mac_address][ATTR_LAST_DATA].get(
|
||||||
|
@@ -11,9 +11,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"identifier_exists": "Application Key and/or API Key already registered",
|
|
||||||
"invalid_key": "Invalid API Key and/or Application Key",
|
"invalid_key": "Invalid API Key and/or Application Key",
|
||||||
"no_devices": "No devices found in account"
|
"no_devices": "No devices found in account"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This app key is already in use."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -109,7 +109,6 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=too-many-ancestors
|
|
||||||
class AmcrestChecker(Http):
|
class AmcrestChecker(Http):
|
||||||
"""amcrest.Http wrapper for catching errors."""
|
"""amcrest.Http wrapper for catching errors."""
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ class AmcrestBinarySensor(BinarySensorDevice):
|
|||||||
|
|
||||||
def __init__(self, name, device, sensor_type):
|
def __init__(self, name, device, sensor_type):
|
||||||
"""Initialize entity."""
|
"""Initialize entity."""
|
||||||
self._name = "{} {}".format(name, BINARY_SENSORS[sensor_type][0])
|
self._name = f"{name} {BINARY_SENSORS[sensor_type][0]}"
|
||||||
self._signal_name = name
|
self._signal_name = name
|
||||||
self._api = device.api
|
self._api = device.api
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
|
@@ -491,9 +491,7 @@ class AmcrestCam(Camera):
|
|||||||
"""Enable or disable indicator light."""
|
"""Enable or disable indicator light."""
|
||||||
try:
|
try:
|
||||||
self._api.command(
|
self._api.command(
|
||||||
"configManager.cgi?action=setConfig&LightGlobal[0].Enable={}".format(
|
f"configManager.cgi?action=setConfig&LightGlobal[0].Enable={str(enable).lower()}"
|
||||||
str(enable).lower()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except AmcrestError as error:
|
except AmcrestError as error:
|
||||||
log_update_error(
|
log_update_error(
|
||||||
|
@@ -6,7 +6,7 @@ def service_signal(service, ident=None):
|
|||||||
"""Encode service and identifier into signal."""
|
"""Encode service and identifier into signal."""
|
||||||
signal = f"{DOMAIN}_{service}"
|
signal = f"{DOMAIN}_{service}"
|
||||||
if ident:
|
if ident:
|
||||||
signal += "_{}".format(ident.replace(".", "_"))
|
signal += f"_{ident.replace('.', '_')}"
|
||||||
return signal
|
return signal
|
||||||
|
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ import logging
|
|||||||
|
|
||||||
from amcrest import AmcrestError
|
from amcrest import AmcrestError
|
||||||
|
|
||||||
from homeassistant.const import CONF_NAME, CONF_SENSORS
|
from homeassistant.const import CONF_NAME, CONF_SENSORS, UNIT_PERCENTAGE
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ SENSOR_SDCARD = "sdcard"
|
|||||||
# Sensor types are defined like: Name, units, icon
|
# Sensor types are defined like: Name, units, icon
|
||||||
SENSORS = {
|
SENSORS = {
|
||||||
SENSOR_PTZ_PRESET: ["PTZ Preset", None, "mdi:camera-iris"],
|
SENSOR_PTZ_PRESET: ["PTZ Preset", None, "mdi:camera-iris"],
|
||||||
SENSOR_SDCARD: ["SD Used", "%", "mdi:sd"],
|
SENSOR_SDCARD: ["SD Used", UNIT_PERCENTAGE, "mdi:sd"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class AmcrestSensor(Entity):
|
|||||||
|
|
||||||
def __init__(self, name, device, sensor_type):
|
def __init__(self, name, device, sensor_type):
|
||||||
"""Initialize a sensor for Amcrest camera."""
|
"""Initialize a sensor for Amcrest camera."""
|
||||||
self._name = "{} {}".format(name, SENSORS[sensor_type][0])
|
self._name = f"{name} {SENSORS[sensor_type][0]}"
|
||||||
self._signal_name = name
|
self._signal_name = name
|
||||||
self._api = device.api
|
self._api = device.api
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
@@ -98,15 +98,21 @@ class AmcrestSensor(Entity):
|
|||||||
elif self._sensor_type == SENSOR_SDCARD:
|
elif self._sensor_type == SENSOR_SDCARD:
|
||||||
storage = self._api.storage_all
|
storage = self._api.storage_all
|
||||||
try:
|
try:
|
||||||
self._attrs["Total"] = "{:.2f} {}".format(*storage["total"])
|
self._attrs[
|
||||||
|
"Total"
|
||||||
|
] = f"{storage['total'][0]:.2f} {storage['total'][1]}"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._attrs["Total"] = "{} {}".format(*storage["total"])
|
self._attrs[
|
||||||
|
"Total"
|
||||||
|
] = f"{storage['total'][0]} {storage['total'][1]}"
|
||||||
try:
|
try:
|
||||||
self._attrs["Used"] = "{:.2f} {}".format(*storage["used"])
|
self._attrs[
|
||||||
|
"Used"
|
||||||
|
] = f"{storage['used'][0]:.2f} {storage['used'][1]}"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._attrs["Used"] = "{} {}".format(*storage["used"])
|
self._attrs["Used"] = f"{storage['used'][0]} {storage['used'][1]}"
|
||||||
try:
|
try:
|
||||||
self._state = "{:.2f}".format(storage["used_percent"])
|
self._state = f"{storage['used_percent']:.2f}"
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._state = storage["used_percent"]
|
self._state = storage["used_percent"]
|
||||||
except AmcrestError as error:
|
except AmcrestError as error:
|
||||||
|
@@ -75,9 +75,7 @@ class PwrCtrlSwitch(SwitchDevice):
|
|||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return the unique ID of the device."""
|
"""Return the unique ID of the device."""
|
||||||
return "{device}-{switch_idx}".format(
|
return f"{self._port.device.host}-{self._port.get_index()}"
|
||||||
device=self._port.device.host, switch_idx=self._port.get_index()
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
@@ -6,7 +6,14 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components import apcupsd
|
from homeassistant.components import apcupsd
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import CONF_RESOURCES, POWER_WATT, TEMP_CELSIUS
|
from homeassistant.const import (
|
||||||
|
CONF_RESOURCES,
|
||||||
|
POWER_WATT,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TIME_MINUTES,
|
||||||
|
TIME_SECONDS,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
@@ -22,7 +29,7 @@ SENSOR_TYPES = {
|
|||||||
"battdate": ["Battery Replaced", "", "mdi:calendar-clock"],
|
"battdate": ["Battery Replaced", "", "mdi:calendar-clock"],
|
||||||
"battstat": ["Battery Status", "", "mdi:information-outline"],
|
"battstat": ["Battery Status", "", "mdi:information-outline"],
|
||||||
"battv": ["Battery Voltage", "V", "mdi:flash"],
|
"battv": ["Battery Voltage", "V", "mdi:flash"],
|
||||||
"bcharge": ["Battery", "%", "mdi:battery"],
|
"bcharge": ["Battery", UNIT_PERCENTAGE, "mdi:battery"],
|
||||||
"cable": ["Cable Type", "", "mdi:ethernet-cable"],
|
"cable": ["Cable Type", "", "mdi:ethernet-cable"],
|
||||||
"cumonbatt": ["Total Time on Battery", "", "mdi:timer"],
|
"cumonbatt": ["Total Time on Battery", "", "mdi:timer"],
|
||||||
"date": ["Status Date", "", "mdi:calendar-clock"],
|
"date": ["Status Date", "", "mdi:calendar-clock"],
|
||||||
@@ -36,20 +43,20 @@ SENSOR_TYPES = {
|
|||||||
"firmware": ["Firmware Version", "", "mdi:information-outline"],
|
"firmware": ["Firmware Version", "", "mdi:information-outline"],
|
||||||
"hitrans": ["Transfer High", "V", "mdi:flash"],
|
"hitrans": ["Transfer High", "V", "mdi:flash"],
|
||||||
"hostname": ["Hostname", "", "mdi:information-outline"],
|
"hostname": ["Hostname", "", "mdi:information-outline"],
|
||||||
"humidity": ["Ambient Humidity", "%", "mdi:water-percent"],
|
"humidity": ["Ambient Humidity", UNIT_PERCENTAGE, "mdi:water-percent"],
|
||||||
"itemp": ["Internal Temperature", TEMP_CELSIUS, "mdi:thermometer"],
|
"itemp": ["Internal Temperature", TEMP_CELSIUS, "mdi:thermometer"],
|
||||||
"lastxfer": ["Last Transfer", "", "mdi:transfer"],
|
"lastxfer": ["Last Transfer", "", "mdi:transfer"],
|
||||||
"linefail": ["Input Voltage Status", "", "mdi:information-outline"],
|
"linefail": ["Input Voltage Status", "", "mdi:information-outline"],
|
||||||
"linefreq": ["Line Frequency", "Hz", "mdi:information-outline"],
|
"linefreq": ["Line Frequency", "Hz", "mdi:information-outline"],
|
||||||
"linev": ["Input Voltage", "V", "mdi:flash"],
|
"linev": ["Input Voltage", "V", "mdi:flash"],
|
||||||
"loadpct": ["Load", "%", "mdi:gauge"],
|
"loadpct": ["Load", UNIT_PERCENTAGE, "mdi:gauge"],
|
||||||
"loadapnt": ["Load Apparent Power", "%", "mdi:gauge"],
|
"loadapnt": ["Load Apparent Power", UNIT_PERCENTAGE, "mdi:gauge"],
|
||||||
"lotrans": ["Transfer Low", "V", "mdi:flash"],
|
"lotrans": ["Transfer Low", "V", "mdi:flash"],
|
||||||
"mandate": ["Manufacture Date", "", "mdi:calendar"],
|
"mandate": ["Manufacture Date", "", "mdi:calendar"],
|
||||||
"masterupd": ["Master Update", "", "mdi:information-outline"],
|
"masterupd": ["Master Update", "", "mdi:information-outline"],
|
||||||
"maxlinev": ["Input Voltage High", "V", "mdi:flash"],
|
"maxlinev": ["Input Voltage High", "V", "mdi:flash"],
|
||||||
"maxtime": ["Battery Timeout", "", "mdi:timer-off"],
|
"maxtime": ["Battery Timeout", "", "mdi:timer-off"],
|
||||||
"mbattchg": ["Battery Shutdown", "%", "mdi:battery-alert"],
|
"mbattchg": ["Battery Shutdown", UNIT_PERCENTAGE, "mdi:battery-alert"],
|
||||||
"minlinev": ["Input Voltage Low", "V", "mdi:flash"],
|
"minlinev": ["Input Voltage Low", "V", "mdi:flash"],
|
||||||
"mintimel": ["Shutdown Time", "", "mdi:timer"],
|
"mintimel": ["Shutdown Time", "", "mdi:timer"],
|
||||||
"model": ["Model", "", "mdi:information-outline"],
|
"model": ["Model", "", "mdi:information-outline"],
|
||||||
@@ -64,7 +71,7 @@ SENSOR_TYPES = {
|
|||||||
"reg1": ["Register 1 Fault", "", "mdi:information-outline"],
|
"reg1": ["Register 1 Fault", "", "mdi:information-outline"],
|
||||||
"reg2": ["Register 2 Fault", "", "mdi:information-outline"],
|
"reg2": ["Register 2 Fault", "", "mdi:information-outline"],
|
||||||
"reg3": ["Register 3 Fault", "", "mdi:information-outline"],
|
"reg3": ["Register 3 Fault", "", "mdi:information-outline"],
|
||||||
"retpct": ["Restore Requirement", "%", "mdi:battery-alert"],
|
"retpct": ["Restore Requirement", UNIT_PERCENTAGE, "mdi:battery-alert"],
|
||||||
"selftest": ["Last Self Test", "", "mdi:calendar-clock"],
|
"selftest": ["Last Self Test", "", "mdi:calendar-clock"],
|
||||||
"sense": ["Sensitivity", "", "mdi:information-outline"],
|
"sense": ["Sensitivity", "", "mdi:information-outline"],
|
||||||
"serialno": ["Serial Number", "", "mdi:information-outline"],
|
"serialno": ["Serial Number", "", "mdi:information-outline"],
|
||||||
@@ -84,16 +91,16 @@ SENSOR_TYPES = {
|
|||||||
|
|
||||||
SPECIFIC_UNITS = {"ITEMP": TEMP_CELSIUS}
|
SPECIFIC_UNITS = {"ITEMP": TEMP_CELSIUS}
|
||||||
INFERRED_UNITS = {
|
INFERRED_UNITS = {
|
||||||
" Minutes": "min",
|
" Minutes": TIME_MINUTES,
|
||||||
" Seconds": "sec",
|
" Seconds": TIME_SECONDS,
|
||||||
" Percent": "%",
|
" Percent": UNIT_PERCENTAGE,
|
||||||
" Volts": "V",
|
" Volts": "V",
|
||||||
" Ampere": "A",
|
" Ampere": "A",
|
||||||
" Volt-Ampere": "VA",
|
" Volt-Ampere": "VA",
|
||||||
" Watts": POWER_WATT,
|
" Watts": POWER_WATT,
|
||||||
" Hz": "Hz",
|
" Hz": "Hz",
|
||||||
" C": TEMP_CELSIUS,
|
" C": TEMP_CELSIUS,
|
||||||
" Percent Load Capacity": "%",
|
" Percent Load Capacity": UNIT_PERCENTAGE,
|
||||||
}
|
}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
|
@@ -26,7 +26,6 @@ from homeassistant.const import (
|
|||||||
URL_API_EVENTS,
|
URL_API_EVENTS,
|
||||||
URL_API_SERVICES,
|
URL_API_SERVICES,
|
||||||
URL_API_STATES,
|
URL_API_STATES,
|
||||||
URL_API_STATES_ENTITY,
|
|
||||||
URL_API_STREAM,
|
URL_API_STREAM,
|
||||||
URL_API_TEMPLATE,
|
URL_API_TEMPLATE,
|
||||||
__version__,
|
__version__,
|
||||||
@@ -254,7 +253,7 @@ class APIEntityStateView(HomeAssistantView):
|
|||||||
status_code = HTTP_CREATED if is_new_state else 200
|
status_code = HTTP_CREATED if is_new_state else 200
|
||||||
resp = self.json(hass.states.get(entity_id), status_code)
|
resp = self.json(hass.states.get(entity_id), status_code)
|
||||||
|
|
||||||
resp.headers.add("Location", URL_API_STATES_ENTITY.format(entity_id))
|
resp.headers.add("Location", f"/api/states/{entity_id}")
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
@@ -88,16 +88,15 @@ def request_configuration(hass, config, atv, credentials):
|
|||||||
try:
|
try:
|
||||||
await atv.airplay.finish_authentication(pin)
|
await atv.airplay.finish_authentication(pin)
|
||||||
hass.components.persistent_notification.async_create(
|
hass.components.persistent_notification.async_create(
|
||||||
"Authentication succeeded!<br /><br />Add the following "
|
f"Authentication succeeded!<br /><br />"
|
||||||
"to credentials: in your apple_tv configuration:<br /><br />"
|
f"Add the following to credentials: "
|
||||||
"{0}".format(credentials),
|
f"in your apple_tv configuration:<br /><br />{credentials}",
|
||||||
title=NOTIFICATION_AUTH_TITLE,
|
title=NOTIFICATION_AUTH_TITLE,
|
||||||
notification_id=NOTIFICATION_AUTH_ID,
|
notification_id=NOTIFICATION_AUTH_ID,
|
||||||
)
|
)
|
||||||
except DeviceAuthenticationError as ex:
|
except DeviceAuthenticationError as ex:
|
||||||
hass.components.persistent_notification.async_create(
|
hass.components.persistent_notification.async_create(
|
||||||
"Authentication failed! Did you enter correct PIN?<br /><br />"
|
f"Authentication failed! Did you enter correct PIN?<br /><br />Details: {ex}",
|
||||||
"Details: {0}".format(ex),
|
|
||||||
title=NOTIFICATION_AUTH_TITLE,
|
title=NOTIFICATION_AUTH_TITLE,
|
||||||
notification_id=NOTIFICATION_AUTH_ID,
|
notification_id=NOTIFICATION_AUTH_ID,
|
||||||
)
|
)
|
||||||
@@ -124,9 +123,7 @@ async def scan_apple_tvs(hass):
|
|||||||
if login_id is None:
|
if login_id is None:
|
||||||
login_id = "Home Sharing disabled"
|
login_id = "Home Sharing disabled"
|
||||||
devices.append(
|
devices.append(
|
||||||
"Name: {0}<br />Host: {1}<br />Login ID: {2}".format(
|
f"Name: {atv.name}<br />Host: {atv.address}<br />Login ID: {login_id}"
|
||||||
atv.name, atv.address, login_id
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not devices:
|
if not devices:
|
||||||
|
@@ -57,7 +57,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
|
|
||||||
def make_filter(callsigns: list) -> str:
|
def make_filter(callsigns: list) -> str:
|
||||||
"""Make a server-side filter from a list of callsigns."""
|
"""Make a server-side filter from a list of callsigns."""
|
||||||
return " ".join("b/{0}".format(cs.upper()) for cs in callsigns)
|
return " ".join(f"b/{sign.upper()}" for sign in callsigns)
|
||||||
|
|
||||||
|
|
||||||
def gps_accuracy(gps, posambiguity: int) -> int:
|
def gps_accuracy(gps, posambiguity: int) -> int:
|
||||||
|
@@ -4,7 +4,12 @@ import logging
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import CONF_MONITORED_CONDITIONS, TEMP_CELSIUS, TEMP_FAHRENHEIT
|
from homeassistant.const import (
|
||||||
|
CONF_MONITORED_CONDITIONS,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
@@ -14,7 +19,7 @@ from . import DOMAIN, UPDATE_TOPIC
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
|
TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
|
||||||
PERCENT_UNITS = ["%", "%"]
|
PERCENT_UNITS = [UNIT_PERCENTAGE, UNIT_PERCENTAGE]
|
||||||
SALT_UNITS = ["g/L", "PPM"]
|
SALT_UNITS = ["g/L", "PPM"]
|
||||||
WATT_UNITS = ["W", "W"]
|
WATT_UNITS = ["W", "W"]
|
||||||
NO_UNITS = [None, None]
|
NO_UNITS = [None, None]
|
||||||
@@ -70,7 +75,7 @@ class AquaLogicSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return "AquaLogic {}".format(SENSOR_TYPES[self._type][0])
|
return f"AquaLogic {SENSOR_TYPES[self._type][0]}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
|
@@ -70,7 +70,7 @@ class AquaLogicSwitch(SwitchDevice):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the switch."""
|
"""Return the name of the switch."""
|
||||||
return "AquaLogic {}".format(SWITCH_TYPES[self._type])
|
return f"AquaLogic {SWITCH_TYPES[self._type]}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
|
@@ -44,9 +44,9 @@ def _optional_zone(value):
|
|||||||
def _zone_name_validator(config):
|
def _zone_name_validator(config):
|
||||||
for zone, zone_config in config[CONF_ZONE].items():
|
for zone, zone_config in config[CONF_ZONE].items():
|
||||||
if CONF_NAME not in zone_config:
|
if CONF_NAME not in zone_config:
|
||||||
zone_config[CONF_NAME] = "{} ({}:{}) - {}".format(
|
zone_config[
|
||||||
DEFAULT_NAME, config[CONF_HOST], config[CONF_PORT], zone
|
CONF_NAME
|
||||||
)
|
] = f"{DEFAULT_NAME} ({config[CONF_HOST]}:{config[CONF_PORT]}) - {zone}"
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@@ -140,7 +140,7 @@ class ArestSensor(Entity):
|
|||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self.arest = arest
|
self.arest = arest
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
self._name = "{} {}".format(location.title(), name.title())
|
self._name = f"{location.title()} {name.title()}"
|
||||||
self._variable = variable
|
self._variable = variable
|
||||||
self._pin = pin
|
self._pin = pin
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -204,8 +204,7 @@ class ArestData:
|
|||||||
try:
|
try:
|
||||||
if str(self._pin[0]) == "A":
|
if str(self._pin[0]) == "A":
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
"{}/analog/{}".format(self._resource, self._pin[1:]),
|
f"{self._resource,}/analog/{self._pin[1:]}", timeout=10
|
||||||
timeout=10,
|
|
||||||
)
|
)
|
||||||
self.data = {"value": response.json()["return_value"]}
|
self.data = {"value": response.json()["return_value"]}
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@@ -86,7 +86,7 @@ class ArestSwitchBase(SwitchDevice):
|
|||||||
def __init__(self, resource, location, name):
|
def __init__(self, resource, location, name):
|
||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
self._name = "{} {}".format(location.title(), name.title())
|
self._name = f"{location.title()} {name.title()}"
|
||||||
self._state = None
|
self._state = None
|
||||||
self._available = True
|
self._available = True
|
||||||
|
|
||||||
|
@@ -67,9 +67,7 @@ def setup(hass, config):
|
|||||||
except (ConnectTimeout, HTTPError) as ex:
|
except (ConnectTimeout, HTTPError) as ex:
|
||||||
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
|
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
|
||||||
hass.components.persistent_notification.create(
|
hass.components.persistent_notification.create(
|
||||||
"Error: {}<br />"
|
f"Error: {ex}<br />You will need to restart hass after fixing.",
|
||||||
"You will need to restart hass after fixing."
|
|
||||||
"".format(ex),
|
|
||||||
title=NOTIFICATION_TITLE,
|
title=NOTIFICATION_TITLE,
|
||||||
notification_id=NOTIFICATION_ID,
|
notification_id=NOTIFICATION_ID,
|
||||||
)
|
)
|
||||||
|
@@ -110,19 +110,19 @@ class ArloBaseStation(AlarmControlPanel):
|
|||||||
else:
|
else:
|
||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
async def async_alarm_disarm(self, code=None):
|
def alarm_disarm(self, code=None):
|
||||||
"""Send disarm command."""
|
"""Send disarm command."""
|
||||||
self._base_station.mode = DISARMED
|
self._base_station.mode = DISARMED
|
||||||
|
|
||||||
async def async_alarm_arm_away(self, code=None):
|
def alarm_arm_away(self, code=None):
|
||||||
"""Send arm away command. Uses custom mode."""
|
"""Send arm away command. Uses custom mode."""
|
||||||
self._base_station.mode = self._away_mode_name
|
self._base_station.mode = self._away_mode_name
|
||||||
|
|
||||||
async def async_alarm_arm_home(self, code=None):
|
def alarm_arm_home(self, code=None):
|
||||||
"""Send arm home command. Uses custom mode."""
|
"""Send arm home command. Uses custom mode."""
|
||||||
self._base_station.mode = self._home_mode_name
|
self._base_station.mode = self._home_mode_name
|
||||||
|
|
||||||
async def async_alarm_arm_night(self, code=None):
|
def alarm_arm_night(self, code=None):
|
||||||
"""Send arm night command. Uses custom mode."""
|
"""Send arm night command. Uses custom mode."""
|
||||||
self._base_station.mode = self._night_mode_name
|
self._base_station.mode = self._night_mode_name
|
||||||
|
|
||||||
|
@@ -78,11 +78,14 @@ class ArloCam(Camera):
|
|||||||
|
|
||||||
async def handle_async_mjpeg_stream(self, request):
|
async def handle_async_mjpeg_stream(self, request):
|
||||||
"""Generate an HTTP MJPEG stream from the camera."""
|
"""Generate an HTTP MJPEG stream from the camera."""
|
||||||
|
video = await self.hass.async_add_executor_job(
|
||||||
|
getattr, self._camera, "last_video"
|
||||||
|
)
|
||||||
|
|
||||||
video = self._camera.last_video
|
|
||||||
if not video:
|
if not video:
|
||||||
error_msg = "Video not found for {0}. Is it older than {1} days?".format(
|
error_msg = (
|
||||||
self.name, self._camera.min_days_vdo_cache
|
f"Video not found for {self.name}. "
|
||||||
|
f"Is it older than {self._camera.min_days_vdo_cache} days?"
|
||||||
)
|
)
|
||||||
_LOGGER.error(error_msg)
|
_LOGGER.error(error_msg)
|
||||||
return
|
return
|
||||||
|
@@ -6,10 +6,12 @@ import voluptuous as vol
|
|||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_MONITORED_CONDITIONS,
|
CONF_MONITORED_CONDITIONS,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
|
UNIT_PERCENTAGE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@@ -26,11 +28,11 @@ SENSOR_TYPES = {
|
|||||||
"last_capture": ["Last", None, "run-fast"],
|
"last_capture": ["Last", None, "run-fast"],
|
||||||
"total_cameras": ["Arlo Cameras", None, "video"],
|
"total_cameras": ["Arlo Cameras", None, "video"],
|
||||||
"captured_today": ["Captured Today", None, "file-video"],
|
"captured_today": ["Captured Today", None, "file-video"],
|
||||||
"battery_level": ["Battery Level", "%", "battery-50"],
|
"battery_level": ["Battery Level", UNIT_PERCENTAGE, "battery-50"],
|
||||||
"signal_strength": ["Signal Strength", None, "signal"],
|
"signal_strength": ["Signal Strength", None, "signal"],
|
||||||
"temperature": ["Temperature", TEMP_CELSIUS, "thermometer"],
|
"temperature": ["Temperature", TEMP_CELSIUS, "thermometer"],
|
||||||
"humidity": ["Humidity", "%", "water-percent"],
|
"humidity": ["Humidity", UNIT_PERCENTAGE, "water-percent"],
|
||||||
"air_quality": ["Air Quality", "ppm", "biohazard"],
|
"air_quality": ["Air Quality", CONCENTRATION_PARTS_PER_MILLION, "biohazard"],
|
||||||
}
|
}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
@@ -57,7 +59,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
if sensor_type in ("temperature", "humidity", "air_quality"):
|
if sensor_type in ("temperature", "humidity", "air_quality"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
name = "{0} {1}".format(SENSOR_TYPES[sensor_type][0], camera.name)
|
name = f"{SENSOR_TYPES[sensor_type][0]} {camera.name}"
|
||||||
sensors.append(ArloSensor(name, camera, sensor_type))
|
sensors.append(ArloSensor(name, camera, sensor_type))
|
||||||
|
|
||||||
for base_station in arlo.base_stations:
|
for base_station in arlo.base_stations:
|
||||||
@@ -65,9 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
sensor_type in ("temperature", "humidity", "air_quality")
|
sensor_type in ("temperature", "humidity", "air_quality")
|
||||||
and base_station.model_id == "ABC1000"
|
and base_station.model_id == "ABC1000"
|
||||||
):
|
):
|
||||||
name = "{0} {1}".format(
|
name = f"{SENSOR_TYPES[sensor_type][0]} {base_station.name}"
|
||||||
SENSOR_TYPES[sensor_type][0], base_station.name
|
|
||||||
)
|
|
||||||
sensors.append(ArloSensor(name, base_station, sensor_type))
|
sensors.append(ArloSensor(name, base_station, sensor_type))
|
||||||
|
|
||||||
add_entities(sensors, True)
|
add_entities(sensors, True)
|
||||||
@@ -83,7 +83,7 @@ class ArloSensor(Entity):
|
|||||||
self._data = device
|
self._data = device
|
||||||
self._sensor_type = sensor_type
|
self._sensor_type = sensor_type
|
||||||
self._state = None
|
self._state = None
|
||||||
self._icon = "mdi:{}".format(SENSOR_TYPES.get(self._sensor_type)[2])
|
self._icon = f"mdi:{SENSOR_TYPES.get(self._sensor_type)[2]}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@@ -141,8 +141,9 @@ class ArloSensor(Entity):
|
|||||||
video = self._data.last_video
|
video = self._data.last_video
|
||||||
self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S")
|
self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S")
|
||||||
except (AttributeError, IndexError):
|
except (AttributeError, IndexError):
|
||||||
error_msg = "Video not found for {0}. Older than {1} days?".format(
|
error_msg = (
|
||||||
self.name, self._data.min_days_vdo_cache
|
f"Video not found for {self.name}. "
|
||||||
|
f"Older than {self._data.min_days_vdo_cache} days?"
|
||||||
)
|
)
|
||||||
_LOGGER.debug(error_msg)
|
_LOGGER.debug(error_msg)
|
||||||
self._state = None
|
self._state = None
|
||||||
|
@@ -84,8 +84,8 @@ class ArubaDeviceScanner(DeviceScanner):
|
|||||||
def get_aruba_data(self):
|
def get_aruba_data(self):
|
||||||
"""Retrieve data from Aruba Access Point and return parsed result."""
|
"""Retrieve data from Aruba Access Point and return parsed result."""
|
||||||
|
|
||||||
connect = "ssh {}@{}"
|
connect = f"ssh {self.username}@{self.host}"
|
||||||
ssh = pexpect.spawn(connect.format(self.username, self.host))
|
ssh = pexpect.spawn(connect)
|
||||||
query = ssh.expect(
|
query = ssh.expect(
|
||||||
[
|
[
|
||||||
"password:",
|
"password:",
|
||||||
|
@@ -50,7 +50,7 @@ def discover_sensors(topic, payload):
|
|||||||
|
|
||||||
|
|
||||||
def _slug(name):
|
def _slug(name):
|
||||||
return "sensor.arwn_{}".format(slugify(name))
|
return f"sensor.arwn_{slugify(name)}"
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||||
|
@@ -49,8 +49,10 @@ class AsteriskCDR(Mailbox):
|
|||||||
"duration": entry["duration"],
|
"duration": entry["duration"],
|
||||||
}
|
}
|
||||||
sha = hashlib.sha256(str(entry).encode("utf-8")).hexdigest()
|
sha = hashlib.sha256(str(entry).encode("utf-8")).hexdigest()
|
||||||
msg = "Destination: {}\nApplication: {}\n Context: {}".format(
|
msg = (
|
||||||
entry["dest"], entry["application"], entry["context"]
|
f"Destination: {entry['dest']}\n"
|
||||||
|
f"Application: {entry['application']}\n "
|
||||||
|
f"Context: {entry['context']}"
|
||||||
)
|
)
|
||||||
cdr.append({"info": info, "sha": sha, "text": msg})
|
cdr.append({"info": info, "sha": sha, "text": msg})
|
||||||
self.cdr = cdr
|
self.cdr = cdr
|
||||||
|
@@ -17,6 +17,8 @@ from homeassistant.helpers.discovery import async_load_platform
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_DNSMASQ = "dnsmasq"
|
||||||
|
CONF_INTERFACE = "interface"
|
||||||
CONF_PUB_KEY = "pub_key"
|
CONF_PUB_KEY = "pub_key"
|
||||||
CONF_REQUIRE_IP = "require_ip"
|
CONF_REQUIRE_IP = "require_ip"
|
||||||
CONF_SENSORS = "sensors"
|
CONF_SENSORS = "sensors"
|
||||||
@@ -24,7 +26,10 @@ CONF_SSH_KEY = "ssh_key"
|
|||||||
|
|
||||||
DOMAIN = "asuswrt"
|
DOMAIN = "asuswrt"
|
||||||
DATA_ASUSWRT = DOMAIN
|
DATA_ASUSWRT = DOMAIN
|
||||||
|
|
||||||
DEFAULT_SSH_PORT = 22
|
DEFAULT_SSH_PORT = 22
|
||||||
|
DEFAULT_INTERFACE = "eth0"
|
||||||
|
DEFAULT_DNSMASQ = "/var/lib/misc"
|
||||||
|
|
||||||
SECRET_GROUP = "Password or SSH Key"
|
SECRET_GROUP = "Password or SSH Key"
|
||||||
SENSOR_TYPES = ["upload_speed", "download_speed", "download", "upload"]
|
SENSOR_TYPES = ["upload_speed", "download_speed", "download", "upload"]
|
||||||
@@ -45,6 +50,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_SENSORS): vol.All(
|
vol.Optional(CONF_SENSORS): vol.All(
|
||||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_INTERFACE, default=DEFAULT_INTERFACE): cv.string,
|
||||||
|
vol.Optional(CONF_DNSMASQ, default=DEFAULT_DNSMASQ): cv.isdir,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -59,13 +66,15 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
api = AsusWrt(
|
api = AsusWrt(
|
||||||
conf[CONF_HOST],
|
conf[CONF_HOST],
|
||||||
conf.get(CONF_PORT),
|
conf[CONF_PORT],
|
||||||
conf.get(CONF_PROTOCOL) == "telnet",
|
conf[CONF_PROTOCOL] == "telnet",
|
||||||
conf[CONF_USERNAME],
|
conf[CONF_USERNAME],
|
||||||
conf.get(CONF_PASSWORD, ""),
|
conf.get(CONF_PASSWORD, ""),
|
||||||
conf.get("ssh_key", conf.get("pub_key", "")),
|
conf.get("ssh_key", conf.get("pub_key", "")),
|
||||||
conf.get(CONF_MODE),
|
conf[CONF_MODE],
|
||||||
conf.get(CONF_REQUIRE_IP),
|
conf[CONF_REQUIRE_IP],
|
||||||
|
conf[CONF_INTERFACE],
|
||||||
|
conf[CONF_DNSMASQ],
|
||||||
)
|
)
|
||||||
|
|
||||||
await api.connection.async_connect()
|
await api.connection.async_connect()
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"domain": "asuswrt",
|
"domain": "asuswrt",
|
||||||
"name": "ASUSWRT",
|
"name": "ASUSWRT",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/asuswrt",
|
"documentation": "https://www.home-assistant.io/integrations/asuswrt",
|
||||||
"requirements": ["aioasuswrt==1.1.22"],
|
"requirements": ["aioasuswrt==1.2.2"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@kennedyshead"]
|
"codeowners": ["@kennedyshead"]
|
||||||
}
|
}
|
||||||
|
32
homeassistant/components/august/.translations/ca.json
Normal file
32
homeassistant/components/august/.translations/ca.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "El compte ja ha estat configurat"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "No s'ha pogut connectar, torna-ho a provar",
|
||||||
|
"invalid_auth": "Autenticaci\u00f3 inv\u00e0lida",
|
||||||
|
"unknown": "Error inesperat"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "M\u00e8tode d'inici de sessi\u00f3",
|
||||||
|
"password": "Contrasenya",
|
||||||
|
"timeout": "Temps d'espera (segons)",
|
||||||
|
"username": "Nom d'usuari"
|
||||||
|
},
|
||||||
|
"description": "Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'email', el nom d'usuari \u00e9s l'adre\u00e7a de correu electr\u00f2nic. Si el m\u00e8tode d'inici de sessi\u00f3 \u00e9s 'phone', el nom d'usuari \u00e9s el n\u00famero de tel\u00e8fon en el format \"+NNNNNNNNN\".",
|
||||||
|
"title": "Configuraci\u00f3 de compte August"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Codi de verificaci\u00f3"
|
||||||
|
},
|
||||||
|
"description": "Comprova el teu {login_method} ({username}) i introdueix el codi de verificaci\u00f3 a continuaci\u00f3",
|
||||||
|
"title": "Autenticaci\u00f3 de dos factors"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/august/.translations/da.json
Normal file
32
homeassistant/components/august/.translations/da.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Kontoen er allerede konfigureret"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Kunne ikke oprette forbindelse. Pr\u00f8v igen",
|
||||||
|
"invalid_auth": "Ugyldig godkendelse",
|
||||||
|
"unknown": "Uventet fejl"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "Loginmetode",
|
||||||
|
"password": "Adgangskode",
|
||||||
|
"timeout": "Timeout (sekunder)",
|
||||||
|
"username": "Brugernavn"
|
||||||
|
},
|
||||||
|
"description": "Hvis loginmetoden er 'e-mail', er brugernavn e-mailadressen. Hvis loginmetoden er 'telefon', er brugernavn telefonnummeret i formatet '+NNNNNNNNNN'.",
|
||||||
|
"title": "Konfigurer en August-konto"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Bekr\u00e6ftelseskode"
|
||||||
|
},
|
||||||
|
"description": "Kontroller dit {login_method} ({username}), og angiv bekr\u00e6ftelseskoden nedenfor",
|
||||||
|
"title": "Tofaktorgodkendelse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
31
homeassistant/components/august/.translations/de.json
Normal file
31
homeassistant/components/august/.translations/de.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Konto ist bereits konfiguriert"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut",
|
||||||
|
"invalid_auth": "Ung\u00fcltige Authentifizierung",
|
||||||
|
"unknown": "Unerwarteter Fehler"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "Anmeldemethode",
|
||||||
|
"password": "Passwort",
|
||||||
|
"timeout": "Zeit\u00fcberschreitung (Sekunden)",
|
||||||
|
"username": "Benutzername"
|
||||||
|
},
|
||||||
|
"title": "Richten Sie ein August-Konto ein"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Verifizierungs-Code"
|
||||||
|
},
|
||||||
|
"description": "Bitte \u00fcberpr\u00fcfen Sie Ihre {login_method} ({username}) und geben Sie den Best\u00e4tigungscode ein",
|
||||||
|
"title": "Zwei-Faktor-Authentifizierung"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/august/.translations/en.json
Normal file
32
homeassistant/components/august/.translations/en.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Account is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect, please try again",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "Login Method",
|
||||||
|
"password": "Password",
|
||||||
|
"timeout": "Timeout (seconds)",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"description": "If the Login Method is 'email', Username is the email address. If the Login Method is 'phone', Username is the phone number in the format '+NNNNNNNNN'.",
|
||||||
|
"title": "Setup an August account"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Verification code"
|
||||||
|
},
|
||||||
|
"description": "Please check your {login_method} ({username}) and enter the verification code below",
|
||||||
|
"title": "Two factor authentication"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/august/.translations/es.json
Normal file
32
homeassistant/components/august/.translations/es.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "La cuenta ya est\u00e1 configurada"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.",
|
||||||
|
"invalid_auth": "Autenticaci\u00f3n no v\u00e1lida",
|
||||||
|
"unknown": "Error inesperado"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "M\u00e9todo de inicio de sesi\u00f3n",
|
||||||
|
"password": "Contrase\u00f1a",
|
||||||
|
"timeout": "Tiempo de espera (segundos)",
|
||||||
|
"username": "Usuario"
|
||||||
|
},
|
||||||
|
"description": "Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'correo electr\u00f3nico', Usuario es la direcci\u00f3n de correo electr\u00f3nico. Si el M\u00e9todo de Inicio de Sesi\u00f3n es 'tel\u00e9fono', Usuario es el n\u00famero de tel\u00e9fono en formato '+NNNNNNNNN'.",
|
||||||
|
"title": "Configurar una cuenta de August"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "C\u00f3digo de verificaci\u00f3n"
|
||||||
|
},
|
||||||
|
"description": "Por favor, compruebe tu {login_method} ({username}) e introduce el c\u00f3digo de verificaci\u00f3n a continuaci\u00f3n",
|
||||||
|
"title": "Autenticaci\u00f3n de dos factores"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/august/.translations/it.json
Normal file
32
homeassistant/components/august/.translations/it.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "L'account \u00e8 gi\u00e0 configurato"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Impossibile connettersi, si prega di riprovare.",
|
||||||
|
"invalid_auth": "Autenticazione non valida",
|
||||||
|
"unknown": "Errore imprevisto"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "Metodo di accesso",
|
||||||
|
"password": "Password",
|
||||||
|
"timeout": "Timeout (in secondi)",
|
||||||
|
"username": "Nome utente"
|
||||||
|
},
|
||||||
|
"description": "Se il metodo di accesso \u00e8 \"e-mail\", il nome utente \u00e8 l'indirizzo e-mail. Se il metodo di accesso \u00e8 \"telefono\", il nome utente \u00e8 il numero di telefono nel formato \"+NNNNNNNNN\".",
|
||||||
|
"title": "Configura un account di August"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Codice di verifica"
|
||||||
|
},
|
||||||
|
"description": "Controlla il tuo {login_method} ({username}) e inserisci il codice di verifica seguente",
|
||||||
|
"title": "Autenticazione a due fattori"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
32
homeassistant/components/august/.translations/lb.json
Normal file
32
homeassistant/components/august/.translations/lb.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Kont ass scho konfigur\u00e9iert"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Feeler beim verbannen, prob\u00e9iert w.e.g. nach emol.",
|
||||||
|
"invalid_auth": "Ong\u00eblteg Authentifikatioun",
|
||||||
|
"unknown": "Onerwaarte Feeler"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"login_method": "Login Method",
|
||||||
|
"password": "Passwuert",
|
||||||
|
"timeout": "Z\u00e4itiwwerscheidung (sekonnen)",
|
||||||
|
"username": "Benotzernumm"
|
||||||
|
},
|
||||||
|
"description": "Wann d'Login Method 'E-Mail' ass, dannn ass de Benotzernumm d'E-Mail Adress. Wann d'Login-Method 'Telefon' ass, ass den Benotzernumm d'Telefonsnummer am Format '+ NNNNNNNNN'.",
|
||||||
|
"title": "August Kont ariichten"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"data": {
|
||||||
|
"code": "Verifikatiouns Code"
|
||||||
|
},
|
||||||
|
"description": "Pr\u00e9ift w.e.g. \u00c4re {login_method} ({username}) a gitt de Verifikatiounscode hei dr\u00ebnner an",
|
||||||
|
"title": "2-Faktor-Authentifikatioun"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "August"
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user