forked from home-assistant/core
Compare commits
646 Commits
otbr_user_
...
sensor_num
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f608d8650 | ||
|
|
65324431a4 | ||
|
|
4dba9c09fc | ||
|
|
35b82db8b0 | ||
|
|
a28e7e1541 | ||
|
|
7f01d57965 | ||
|
|
0d018d53f1 | ||
|
|
be25d17e02 | ||
|
|
7db166b515 | ||
|
|
33ede351f0 | ||
|
|
1c4ba61725 | ||
|
|
f4654128db | ||
|
|
be69c81db5 | ||
|
|
f8c6e4c20a | ||
|
|
d88849fb04 | ||
|
|
e706696271 | ||
|
|
53638ba138 | ||
|
|
47453420c1 | ||
|
|
0c383e28c4 | ||
|
|
98b4a412f7 | ||
|
|
da35ac1942 | ||
|
|
365ce55d77 | ||
|
|
28affe91be | ||
|
|
2cdeb6f1cd | ||
|
|
7e206b5854 | ||
|
|
1edd00c51f | ||
|
|
772df02cce | ||
|
|
8337d4613e | ||
|
|
50373500c3 | ||
|
|
a8b23d7139 | ||
|
|
c715534821 | ||
|
|
e0f8b5bbd1 | ||
|
|
f7fdaadde0 | ||
|
|
e57dad79fc | ||
|
|
00118a6f96 | ||
|
|
f874258e7e | ||
|
|
53c5f02ca2 | ||
|
|
857df05308 | ||
|
|
d22e670334 | ||
|
|
2c12171e25 | ||
|
|
98a79dd5a2 | ||
|
|
73cd03255f | ||
|
|
d4489faa68 | ||
|
|
5f57648578 | ||
|
|
21d1c647c5 | ||
|
|
032a37b121 | ||
|
|
3b5fd4bd06 | ||
|
|
7368c86ecb | ||
|
|
7e7a27f8b9 | ||
|
|
cc36848a6d | ||
|
|
d2e75e4f7a | ||
|
|
d485630ce9 | ||
|
|
a4fa0925b7 | ||
|
|
c56832bb2c | ||
|
|
9ac8f9aa37 | ||
|
|
709d1cb8af | ||
|
|
1958dd5550 | ||
|
|
92a833b192 | ||
|
|
4e9bd09d39 | ||
|
|
b82ecfdd28 | ||
|
|
0f4b17755e | ||
|
|
c612a92cfb | ||
|
|
80ffac48a3 | ||
|
|
4f965f0eca | ||
|
|
569bf3bb76 | ||
|
|
f9e8247401 | ||
|
|
344a0c55a5 | ||
|
|
11e125f2e8 | ||
|
|
691a234090 | ||
|
|
eebc338c3b | ||
|
|
58de7b8df0 | ||
|
|
b1e939d1f1 | ||
|
|
ec3475910f | ||
|
|
799edd90aa | ||
|
|
0f6f63da64 | ||
|
|
e93bfa6556 | ||
|
|
6a94a58325 | ||
|
|
a7ddd592fb | ||
|
|
733798f483 | ||
|
|
af5fd74d6f | ||
|
|
efd2817221 | ||
|
|
d97a061285 | ||
|
|
e2edbc4259 | ||
|
|
d0c7f42559 | ||
|
|
b69576d6de | ||
|
|
adb0d85511 | ||
|
|
072f228e4d | ||
|
|
803cd8d9a3 | ||
|
|
50c2992f36 | ||
|
|
15492aead0 | ||
|
|
661f7e1522 | ||
|
|
e9543a7254 | ||
|
|
8f74bff354 | ||
|
|
f9f9741d2a | ||
|
|
ef800335fb | ||
|
|
8c993116e1 | ||
|
|
bfbf9b9751 | ||
|
|
89c0b27b42 | ||
|
|
561fc2d771 | ||
|
|
3ff3834cae | ||
|
|
a79885ceaf | ||
|
|
57cf11f067 | ||
|
|
49148421cb | ||
|
|
fca3382d37 | ||
|
|
42c4f2f7fa | ||
|
|
ae6bc96002 | ||
|
|
e4a78420b8 | ||
|
|
bd9a8ba6f1 | ||
|
|
78207121c0 | ||
|
|
687184138c | ||
|
|
e738924780 | ||
|
|
71d7098530 | ||
|
|
020d52c3e2 | ||
|
|
b84cf3a3d9 | ||
|
|
2c8cb13034 | ||
|
|
9cd48b4999 | ||
|
|
04bc522fa5 | ||
|
|
caa1ba7e13 | ||
|
|
138a522d2e | ||
|
|
62dcbe5258 | ||
|
|
7ed9967245 | ||
|
|
b3380261d7 | ||
|
|
eb5d63237c | ||
|
|
2bef69c6a7 | ||
|
|
0d579f6ac3 | ||
|
|
8a5a1b810a | ||
|
|
95d0329d6c | ||
|
|
d4955a3d87 | ||
|
|
adeaf746ec | ||
|
|
38203003d2 | ||
|
|
25c451832b | ||
|
|
468457eff4 | ||
|
|
7af86fe130 | ||
|
|
54fcf58449 | ||
|
|
c727f403ff | ||
|
|
4181a9baf0 | ||
|
|
6e285c87c3 | ||
|
|
2e70de9dd9 | ||
|
|
6ea234ed57 | ||
|
|
282d6af2a2 | ||
|
|
484e73beaa | ||
|
|
9a6e620810 | ||
|
|
1139555448 | ||
|
|
cbcff6435f | ||
|
|
fea30c1ce9 | ||
|
|
b9ffc67a44 | ||
|
|
ca8cc284ed | ||
|
|
ff91fb7d74 | ||
|
|
021ac84405 | ||
|
|
17d3159e77 | ||
|
|
fa7ad20372 | ||
|
|
28a3b4a32c | ||
|
|
c395698ea2 | ||
|
|
e06603bbbd | ||
|
|
4cafd393c6 | ||
|
|
e50a531cd9 | ||
|
|
7ab88fa713 | ||
|
|
4955dd3e1b | ||
|
|
1d1d69ca02 | ||
|
|
a56f6cb863 | ||
|
|
7d672b4a4d | ||
|
|
df0fc30695 | ||
|
|
1b97a51b5e | ||
|
|
74ae351ac0 | ||
|
|
03a8dcfdc1 | ||
|
|
c5c7bb36cc | ||
|
|
c5c68cd429 | ||
|
|
7d641e4d3e | ||
|
|
a85c4a1ddf | ||
|
|
17f85165c8 | ||
|
|
bbed1099d5 | ||
|
|
7ddb467ba6 | ||
|
|
b2004e62b1 | ||
|
|
0ccab19d2c | ||
|
|
f182e314e5 | ||
|
|
23c9580a4a | ||
|
|
a1ed2a57eb | ||
|
|
7ff1265b10 | ||
|
|
5f1edbccd1 | ||
|
|
665a2889ec | ||
|
|
0cf676d501 | ||
|
|
65a44cad8f | ||
|
|
3007e0259d | ||
|
|
f327a247a0 | ||
|
|
a2fb6fbaa8 | ||
|
|
0eabc27982 | ||
|
|
82f006bc5c | ||
|
|
890c2277ca | ||
|
|
6270f33bee | ||
|
|
f3cf760772 | ||
|
|
d0b67689e0 | ||
|
|
60b799aac9 | ||
|
|
0e9b74986f | ||
|
|
a8c952f82f | ||
|
|
5a77a2801b | ||
|
|
781a4267cf | ||
|
|
7bd56ad7d6 | ||
|
|
540eb8de16 | ||
|
|
33c777529e | ||
|
|
b253bb841e | ||
|
|
f548ccfb92 | ||
|
|
1a2652e1bb | ||
|
|
5285d057d2 | ||
|
|
3bfdba50d9 | ||
|
|
e427a70dc7 | ||
|
|
c1e3c1a27c | ||
|
|
dae7bcf387 | ||
|
|
b76c0c6f08 | ||
|
|
086a6460ef | ||
|
|
ba63a9600e | ||
|
|
e832ef78f8 | ||
|
|
6c8efe3a3b | ||
|
|
5c6656dcac | ||
|
|
9636fe4602 | ||
|
|
a851b20c97 | ||
|
|
4b427ec02c | ||
|
|
b507fb1e06 | ||
|
|
a4c52567a7 | ||
|
|
2c9e8ad475 | ||
|
|
52afdb4a8b | ||
|
|
099b844dce | ||
|
|
09891ead8d | ||
|
|
da390dbd9a | ||
|
|
dbab57ba87 | ||
|
|
b91a0d21d2 | ||
|
|
c599d1e1f8 | ||
|
|
b3c5c6ae9c | ||
|
|
886d2fc3a1 | ||
|
|
90fc8dd860 | ||
|
|
4b88a71d60 | ||
|
|
ff5c1ce2d3 | ||
|
|
9a68f0abe8 | ||
|
|
b89a51c63d | ||
|
|
949c88930f | ||
|
|
0d3bf0e911 | ||
|
|
c3c290b576 | ||
|
|
0daaa37e09 | ||
|
|
80a8da26bc | ||
|
|
310d7718a0 | ||
|
|
6cad0c7984 | ||
|
|
b4ddff751a | ||
|
|
d684aa4225 | ||
|
|
43f3b0f933 | ||
|
|
58e8f53117 | ||
|
|
d36d98937d | ||
|
|
f8c0e80ef7 | ||
|
|
0530f61373 | ||
|
|
02e973026d | ||
|
|
e717f56113 | ||
|
|
73c4ac53d2 | ||
|
|
c9499f6574 | ||
|
|
d703a83412 | ||
|
|
e96cea997e | ||
|
|
44beb350cd | ||
|
|
a9533c72fc | ||
|
|
65ad953497 | ||
|
|
8d678209db | ||
|
|
1b1f8a1d61 | ||
|
|
1b4fda2321 | ||
|
|
14d3911bfd | ||
|
|
09ca8465a6 | ||
|
|
c97cf62b47 | ||
|
|
63bddae01d | ||
|
|
66f12d7dab | ||
|
|
bf41a971a2 | ||
|
|
df0c0297c8 | ||
|
|
9f5b1e58cb | ||
|
|
b7de185924 | ||
|
|
22dee1f92b | ||
|
|
e084fe4903 | ||
|
|
b29425a9eb | ||
|
|
42ca46d7b2 | ||
|
|
3f4c8a28ec | ||
|
|
9fb4f6b643 | ||
|
|
8981e4820a | ||
|
|
5f0adfe6e4 | ||
|
|
3ec7f0280e | ||
|
|
2ab3d3ebf5 | ||
|
|
ea95abcb30 | ||
|
|
6d811d3fdb | ||
|
|
66e21d7701 | ||
|
|
60894c33a7 | ||
|
|
613aa6f43a | ||
|
|
94c7f7bbb7 | ||
|
|
7d1dec8d31 | ||
|
|
978aafdd09 | ||
|
|
e15aaf2853 | ||
|
|
c1332f68b3 | ||
|
|
e3b81ad170 | ||
|
|
51001ad1e1 | ||
|
|
295308c39c | ||
|
|
b4dd1b8cb2 | ||
|
|
15ab04f97d | ||
|
|
33fb27eb1a | ||
|
|
9ef86b7b66 | ||
|
|
c15f4ad648 | ||
|
|
7c8f6e9fad | ||
|
|
8672be3829 | ||
|
|
052145fabd | ||
|
|
6f94e47270 | ||
|
|
68dd2802a1 | ||
|
|
74a76c6fe7 | ||
|
|
f719ecf086 | ||
|
|
00e5f23249 | ||
|
|
6582ee3591 | ||
|
|
29e3d06a42 | ||
|
|
b03677db1c | ||
|
|
ea43effcc9 | ||
|
|
a1a324a02e | ||
|
|
fb81b1791f | ||
|
|
2c9d0b7f18 | ||
|
|
077ca97ef8 | ||
|
|
bfc19c8cc3 | ||
|
|
64535175b1 | ||
|
|
d0153f5031 | ||
|
|
ab76b3ffb3 | ||
|
|
4f87c1f30f | ||
|
|
6397138589 | ||
|
|
da35097803 | ||
|
|
40be2324cc | ||
|
|
8abce25948 | ||
|
|
b0ed0d5d41 | ||
|
|
f57c0ea725 | ||
|
|
1eec87214f | ||
|
|
d7dda6bee5 | ||
|
|
45b4b0e990 | ||
|
|
af107d7853 | ||
|
|
0c1abd5f10 | ||
|
|
8bc303a562 | ||
|
|
4d215e573c | ||
|
|
70a9c8f8aa | ||
|
|
66c3115b26 | ||
|
|
b1ae7d409b | ||
|
|
9e4be56939 | ||
|
|
32c1a01159 | ||
|
|
9c76cd1b6a | ||
|
|
5102d1a5f3 | ||
|
|
7661b222b4 | ||
|
|
7729a5cf8a | ||
|
|
a22041c9be | ||
|
|
52ea64d1d0 | ||
|
|
711c92a87f | ||
|
|
71c4588747 | ||
|
|
79a3d2e6f6 | ||
|
|
974601cc2e | ||
|
|
71d54da673 | ||
|
|
9669b286c4 | ||
|
|
72dae914fc | ||
|
|
0b0e977ce9 | ||
|
|
332d3e0f19 | ||
|
|
8e117ee499 | ||
|
|
30bf0634fe | ||
|
|
a0810053f1 | ||
|
|
504a3d3028 | ||
|
|
e0d0dc05e4 | ||
|
|
bcec69fec1 | ||
|
|
e1fc494b54 | ||
|
|
b9a7b908f3 | ||
|
|
5ff0479c16 | ||
|
|
4c84824ac8 | ||
|
|
8cf6ebd363 | ||
|
|
c842666bea | ||
|
|
8227c84e05 | ||
|
|
0657c99efd | ||
|
|
771e07c68b | ||
|
|
d5797d9f7d | ||
|
|
164fad112c | ||
|
|
aac89a3493 | ||
|
|
53b931e21a | ||
|
|
ca4d7634a8 | ||
|
|
2dca826fa9 | ||
|
|
a49461a040 | ||
|
|
a62b8a4f5b | ||
|
|
772a432c4d | ||
|
|
5306883288 | ||
|
|
402be4ebde | ||
|
|
f608e150fd | ||
|
|
91c502ae55 | ||
|
|
e8d19e7c62 | ||
|
|
255611238b | ||
|
|
e1483ff746 | ||
|
|
1e2f00e186 | ||
|
|
0c8b6c13fc | ||
|
|
50800d2590 | ||
|
|
be5fe29dc9 | ||
|
|
58bfeb3110 | ||
|
|
80929c5f8c | ||
|
|
7e51aeb916 | ||
|
|
7f4a727e10 | ||
|
|
df77646c8a | ||
|
|
914704e459 | ||
|
|
7e8c081065 | ||
|
|
a9728bd3a5 | ||
|
|
c14aa7bee4 | ||
|
|
db6cacafcb | ||
|
|
ae39b95bb1 | ||
|
|
d94f007dbf | ||
|
|
24fdd588fd | ||
|
|
79b52a2b41 | ||
|
|
29b2b6727e | ||
|
|
658db7ff05 | ||
|
|
e1512fd3e1 | ||
|
|
92742ae423 | ||
|
|
c8b9260f92 | ||
|
|
585c4acfee | ||
|
|
3c4455c696 | ||
|
|
4be7b62607 | ||
|
|
afb704f607 | ||
|
|
656632f504 | ||
|
|
9a4329aa1d | ||
|
|
f00aadfc25 | ||
|
|
59ad232ce5 | ||
|
|
5aca996f22 | ||
|
|
8f10c22a23 | ||
|
|
aa7e051538 | ||
|
|
82a13740b3 | ||
|
|
8dd0752bd0 | ||
|
|
58beab1b59 | ||
|
|
2c127c00d4 | ||
|
|
67f7a9ea78 | ||
|
|
15a35004dd | ||
|
|
d935f9400d | ||
|
|
8aeb20db00 | ||
|
|
9631146745 | ||
|
|
c0d9dcdb3f | ||
|
|
40d39a15c9 | ||
|
|
200d3ae845 | ||
|
|
6802f3db30 | ||
|
|
3f348714e2 | ||
|
|
bcd4c031c6 | ||
|
|
6f44bd43b0 | ||
|
|
74096b87eb | ||
|
|
4b6157cd9b | ||
|
|
2f98485ae7 | ||
|
|
ca885f3fab | ||
|
|
d1ecc418bb | ||
|
|
353638426e | ||
|
|
0dabbcfca1 | ||
|
|
37c1052cce | ||
|
|
a83318f373 | ||
|
|
4f63398941 | ||
|
|
29337bc6eb | ||
|
|
c40c37e9ee | ||
|
|
f2b348dbdf | ||
|
|
e43802eb07 | ||
|
|
4bebf00598 | ||
|
|
5e6ba594aa | ||
|
|
f0ba7a3795 | ||
|
|
fea5330cee | ||
|
|
141acba40d | ||
|
|
bc115634d1 | ||
|
|
1cc8feabb7 | ||
|
|
6d336ec136 | ||
|
|
d26484d482 | ||
|
|
a44e44b7d0 | ||
|
|
9cdf7a09ed | ||
|
|
1cfcc9313b | ||
|
|
a87a9790e9 | ||
|
|
382e1ac679 | ||
|
|
f17a829bd8 | ||
|
|
767b43bb0e | ||
|
|
9d9817328b | ||
|
|
0b45fb6dc3 | ||
|
|
33d0dec648 | ||
|
|
5279535046 | ||
|
|
87b2a73460 | ||
|
|
91aaca6471 | ||
|
|
b722a7e05b | ||
|
|
cc74fcbda7 | ||
|
|
1c2510bfb3 | ||
|
|
829df7ddfd | ||
|
|
f93bbd55ba | ||
|
|
f6cd399b9e | ||
|
|
27359dfc89 | ||
|
|
b4abfb1697 | ||
|
|
bd1c476edf | ||
|
|
cf68d081ca | ||
|
|
65c4e63e30 | ||
|
|
25392655e7 | ||
|
|
096ef5da47 | ||
|
|
c5fb3e7fab | ||
|
|
669e6202ad | ||
|
|
6a7e6ad0fd | ||
|
|
5656129b60 | ||
|
|
96578f3f89 | ||
|
|
4138e518ef | ||
|
|
aa43acb443 | ||
|
|
b459261ef2 | ||
|
|
a318576c4f | ||
|
|
9a6aaea9db | ||
|
|
627ded42f5 | ||
|
|
fa09eba165 | ||
|
|
fcf53668c5 | ||
|
|
d61b915286 | ||
|
|
b0153c7deb | ||
|
|
2447e24677 | ||
|
|
502fea5f95 | ||
|
|
caa8f9e49b | ||
|
|
8beb043d62 | ||
|
|
cb27cfe7dd | ||
|
|
1d5ecdd4ea | ||
|
|
3bb9be2382 | ||
|
|
6581bad7ce | ||
|
|
197634503f | ||
|
|
32fc0e03a5 | ||
|
|
2e9ea0c934 | ||
|
|
856f68252b | ||
|
|
2789747b0f | ||
|
|
45d14739c5 | ||
|
|
d0f95d84b4 | ||
|
|
1e852e761c | ||
|
|
c3859f9170 | ||
|
|
6df4fc6708 | ||
|
|
b297b78086 | ||
|
|
4bdf87d383 | ||
|
|
e47364f34d | ||
|
|
fe7d32dc5d | ||
|
|
62a003a053 | ||
|
|
b5d1421dfd | ||
|
|
ebab2bd0f9 | ||
|
|
e7babb4266 | ||
|
|
1a042c2dad | ||
|
|
731ca046f6 | ||
|
|
c844276e95 | ||
|
|
9f9cdb62eb | ||
|
|
c73830439f | ||
|
|
940b5d62b4 | ||
|
|
b3454bfd9c | ||
|
|
834847988d | ||
|
|
caf15534bb | ||
|
|
10cb2e31c4 | ||
|
|
85c9f9facf | ||
|
|
5ff7b3bb1a | ||
|
|
e5ba423d6d | ||
|
|
b30d4ef7cf | ||
|
|
00e563f1b8 | ||
|
|
cf06f3b81d | ||
|
|
a781fcca86 | ||
|
|
764550f2e1 | ||
|
|
7396bcc585 | ||
|
|
7e6b087773 | ||
|
|
71ce7373a3 | ||
|
|
33bb9c230b | ||
|
|
f0f2c12d91 | ||
|
|
2840821594 | ||
|
|
edfd83c3a7 | ||
|
|
ee88f34a91 | ||
|
|
fa4c250001 | ||
|
|
59d6f827c3 | ||
|
|
26ea02aa8f | ||
|
|
d73b86132b | ||
|
|
8034faadca | ||
|
|
3c2b7c0d69 | ||
|
|
563ad02c65 | ||
|
|
fe89b663e7 | ||
|
|
dcd07d3135 | ||
|
|
8bf2299407 | ||
|
|
9c689d757c | ||
|
|
4e4fc1767f | ||
|
|
cc3c5772c5 | ||
|
|
6ba6991ecd | ||
|
|
d52d068469 | ||
|
|
09b3611a63 | ||
|
|
ab2f05d3e9 | ||
|
|
90ac0c870f | ||
|
|
0fd113db59 | ||
|
|
1b43323f5e | ||
|
|
6108e581b1 | ||
|
|
c8c68f05ec | ||
|
|
b80467dc58 | ||
|
|
6e9f0eca03 | ||
|
|
cc6a2f0338 | ||
|
|
6ebf2ec9ec | ||
|
|
9ecee11af6 | ||
|
|
9a1669103b | ||
|
|
368ea0586d | ||
|
|
4a7db6ee71 | ||
|
|
a10b9572c7 | ||
|
|
0b47bf1f0b | ||
|
|
5f4d286556 | ||
|
|
b23ab3c65a | ||
|
|
7c199b36f8 | ||
|
|
d4e55ee030 | ||
|
|
f3ec82543e | ||
|
|
4013d4c48d | ||
|
|
93ac908776 | ||
|
|
2ad1a53038 | ||
|
|
3ba59fbebe | ||
|
|
f3fab5c1f5 | ||
|
|
2d120cb6ba | ||
|
|
ad782166c7 | ||
|
|
bc9202cf02 | ||
|
|
0d385d3b67 | ||
|
|
76fa24aba1 | ||
|
|
95ae37cd87 | ||
|
|
bc1d22f4ec | ||
|
|
67e1872ab6 | ||
|
|
516c2b0cdb | ||
|
|
60f067b68f | ||
|
|
ff76567061 | ||
|
|
93488cfa0f | ||
|
|
9655619667 | ||
|
|
32736b3336 | ||
|
|
c77b78928e | ||
|
|
a7ba242f1f | ||
|
|
043d58d697 | ||
|
|
6408890543 | ||
|
|
c5f7d7ae85 | ||
|
|
7ab27cd9bf | ||
|
|
9932c0cb91 | ||
|
|
565d4f85c1 | ||
|
|
7be60d4569 | ||
|
|
a50622cbfd | ||
|
|
fb41b024c0 | ||
|
|
80ac4c0269 | ||
|
|
0e0677b690 | ||
|
|
50d9e3efe6 | ||
|
|
ca28006d76 | ||
|
|
ac3711e6ab | ||
|
|
5901964bf6 | ||
|
|
b24c40f2df | ||
|
|
2cb7a80f98 | ||
|
|
f05de2b28c | ||
|
|
d9aff9d7b0 | ||
|
|
46e11c2fa8 | ||
|
|
522477d5a4 | ||
|
|
cc2b592221 | ||
|
|
bd86111dd8 | ||
|
|
f0514008fa | ||
|
|
3c8a66abbe | ||
|
|
f1d509be03 | ||
|
|
fbdc7d44bc | ||
|
|
cae386465e | ||
|
|
31847e3a69 | ||
|
|
5787e1506c | ||
|
|
9a29f64128 | ||
|
|
6c0e4a9e8f | ||
|
|
9ce64f8990 | ||
|
|
6ef4086683 | ||
|
|
b146f52317 | ||
|
|
001bd78bcb | ||
|
|
ba4ec8f8c1 | ||
|
|
8aa3a6cc15 | ||
|
|
ed43e1d3a4 | ||
|
|
b7654c0fce | ||
|
|
99f3cfdf8a |
263
.coveragerc
263
.coveragerc
File diff suppressed because it is too large
Load Diff
@@ -45,13 +45,5 @@
|
||||
"!include_dir_merge_list scalar",
|
||||
"!include_dir_merge_named scalar"
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/sshd:1": {
|
||||
"version": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: 3.9
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
|
||||
jobs:
|
||||
init:
|
||||
|
||||
238
.github/workflows/ci.yaml
vendored
238
.github/workflows/ci.yaml
vendored
@@ -18,13 +18,22 @@ on:
|
||||
description: "Skip pytest"
|
||||
default: false
|
||||
type: boolean
|
||||
pylint-only:
|
||||
description: "Only run pylint"
|
||||
default: false
|
||||
type: boolean
|
||||
mypy-only:
|
||||
description: "Only run mypy"
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
PIP_CACHE_VERSION: 3
|
||||
HA_SHORT_VERSION: 2023.2
|
||||
DEFAULT_PYTHON: 3.9
|
||||
ALL_PYTHON_VERSIONS: "['3.9', '3.10']"
|
||||
MYPY_CACHE_VERSION: 3
|
||||
HA_SHORT_VERSION: 2023.3
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
ALL_PYTHON_VERSIONS: "['3.10']"
|
||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||
PIP_CACHE: /tmp/pip-cache
|
||||
SQLALCHEMY_WARN_20: 1
|
||||
@@ -163,6 +172,9 @@ jobs:
|
||||
pre-commit:
|
||||
name: Prepare pre-commit base
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
steps:
|
||||
@@ -176,7 +188,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.2.3
|
||||
uses: actions/cache@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -191,7 +203,7 @@ jobs:
|
||||
pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.2.3
|
||||
uses: actions/cache@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: >-
|
||||
@@ -220,30 +232,22 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if pre-commit cache restore failed
|
||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore pre-commit environment from cache"
|
||||
exit 1
|
||||
- name: Run black (fully)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
@@ -274,30 +278,22 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if pre-commit cache restore failed
|
||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore pre-commit environment from cache"
|
||||
exit 1
|
||||
- name: Register flake8 problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/flake8.json"
|
||||
@@ -314,6 +310,55 @@ jobs:
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*}
|
||||
|
||||
lint-ruff:
|
||||
name: Check ruff
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- info
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.3.0
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.5.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Register ruff problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/ruff.json"
|
||||
- name: Run ruff (fully)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual ruff --all-files
|
||||
- name: Run ruff (partially)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual ruff --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*}
|
||||
|
||||
lint-isort:
|
||||
name: Check isort
|
||||
runs-on: ubuntu-20.04
|
||||
@@ -331,30 +376,22 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if pre-commit cache restore failed
|
||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore pre-commit environment from cache"
|
||||
exit 1
|
||||
- name: Run isort
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -377,30 +414,22 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Fail job if pre-commit cache restore failed
|
||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore pre-commit environment from cache"
|
||||
exit 1
|
||||
|
||||
- name: Run pyupgrade (fully)
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
@@ -509,7 +538,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.2.3
|
||||
uses: actions/cache@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -517,7 +546,7 @@ jobs:
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore pip wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3.2.3
|
||||
uses: actions/cache@v3.2.4
|
||||
with:
|
||||
path: ${{ env.PIP_CACHE }}
|
||||
key: >-
|
||||
@@ -554,6 +583,9 @@ jobs:
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -568,17 +600,13 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Run hassfest
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -587,6 +615,9 @@ jobs:
|
||||
gen-requirements-all:
|
||||
name: Check all requirements
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -601,17 +632,13 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Run gen_requirements_all.py
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -621,6 +648,9 @@ jobs:
|
||||
name: Check pylint
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
if: |
|
||||
github.event.inputs.mypy-only != 'true'
|
||||
|| github.event.inputs.pylint-only == 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -635,17 +665,13 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Register pylint problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/pylint.json"
|
||||
@@ -666,6 +692,9 @@ jobs:
|
||||
mypy:
|
||||
name: Check mypy
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
|| github.event.inputs.mypy-only == 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -678,19 +707,33 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Generate partial mypy restore key
|
||||
id: generate-mypy-key
|
||||
run: |
|
||||
mypy_version=$(cat requirements_test.txt | grep mypy | cut -d '=' -f 3)
|
||||
echo "version=$mypy_version" >> $GITHUB_OUTPUT
|
||||
echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-${{
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@v3.2.4
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
steps.generate-mypy-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-mypy-${{
|
||||
env.MYPY_CACHE_VERSION }}-${{ steps.generate-mypy-key.outputs.version }}-${{
|
||||
env.HA_SHORT_VERSION }}-
|
||||
- name: Register mypy problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/mypy.json"
|
||||
@@ -710,6 +753,9 @@ jobs:
|
||||
|
||||
pip-check:
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -729,17 +775,13 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Run pip check
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -750,6 +792,8 @@ jobs:
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob)
|
||||
needs:
|
||||
- info
|
||||
@@ -757,8 +801,9 @@ jobs:
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-black
|
||||
- lint-other
|
||||
- lint-isort
|
||||
- lint-other
|
||||
- lint-ruff
|
||||
- mypy
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -784,16 +829,12 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Register Python problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
@@ -873,6 +914,8 @@ jobs:
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& needs.info.outputs.test_full_suite == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -880,8 +923,9 @@ jobs:
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-black
|
||||
- lint-other
|
||||
- lint-isort
|
||||
- lint-other
|
||||
- lint-ruff
|
||||
- mypy
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -907,16 +951,12 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v3.2.3
|
||||
uses: actions/cache/restore@v3.2.4
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Fail job if Python cache restore failed
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Register Python problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/python.json"
|
||||
|
||||
30
.github/workflows/matchers/ruff.json
vendored
Normal file
30
.github/workflows/matchers/ruff.json
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "ruff-error",
|
||||
"severity": "error",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s([EF]\\d{3}\\s.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "ruff-warning",
|
||||
"severity": "warning",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+):(\\d+):\\s([CDNW]\\d{3}\\s.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
id: token
|
||||
# Pinned to a specific version of the action for security reasons
|
||||
# v1.7.0
|
||||
uses: tibdex/github-app-token@021a2405c7f990db57f5eae5397423dcc554159c
|
||||
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
|
||||
with:
|
||||
app_id: ${{ secrets.ISSUE_TRIAGE_APP_ID }}
|
||||
private_key: ${{ secrets.ISSUE_TRIAGE_APP_PEM }}
|
||||
|
||||
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: 3.9
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.237
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
- --fix
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
args: [--py310-plus]
|
||||
stages: [manual]
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.0.0
|
||||
hooks:
|
||||
@@ -11,6 +18,7 @@ repos:
|
||||
args:
|
||||
- --in-place
|
||||
- --remove-all-unused-imports
|
||||
stages: [manual]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.12.0
|
||||
hooks:
|
||||
@@ -41,6 +49,7 @@ repos:
|
||||
- flake8-noqa==1.3.0
|
||||
- mccabe==0.7.0
|
||||
exclude: docs/source/conf.py
|
||||
stages: [manual]
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.7.4
|
||||
hooks:
|
||||
@@ -51,11 +60,11 @@ repos:
|
||||
- --configfile=tests/bandit.yaml
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.11.4
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
stages: [manual]
|
||||
@@ -84,7 +93,7 @@ repos:
|
||||
- id: python-typing-update
|
||||
stages: [manual]
|
||||
args:
|
||||
- --py39-plus
|
||||
- --py310-plus
|
||||
- --force
|
||||
- --keep-updates
|
||||
files: ^(homeassistant|tests|script)/.+\.py$
|
||||
|
||||
@@ -112,6 +112,7 @@ homeassistant.components.fastdotcom.*
|
||||
homeassistant.components.feedreader.*
|
||||
homeassistant.components.file_upload.*
|
||||
homeassistant.components.filesize.*
|
||||
homeassistant.components.filter.*
|
||||
homeassistant.components.fitbit.*
|
||||
homeassistant.components.flux_led.*
|
||||
homeassistant.components.forecast_solar.*
|
||||
@@ -174,6 +175,7 @@ homeassistant.components.jewish_calendar.*
|
||||
homeassistant.components.kaleidescape.*
|
||||
homeassistant.components.knx.*
|
||||
homeassistant.components.kraken.*
|
||||
homeassistant.components.lacrosse.*
|
||||
homeassistant.components.lacrosse_view.*
|
||||
homeassistant.components.lametric.*
|
||||
homeassistant.components.laundrify.*
|
||||
@@ -201,6 +203,7 @@ homeassistant.components.mjpeg.*
|
||||
homeassistant.components.modbus.*
|
||||
homeassistant.components.modem_callerid.*
|
||||
homeassistant.components.moon.*
|
||||
homeassistant.components.mopeka.*
|
||||
homeassistant.components.mqtt.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.nam.*
|
||||
@@ -222,6 +225,7 @@ homeassistant.components.onewire.*
|
||||
homeassistant.components.open_meteo.*
|
||||
homeassistant.components.openexchangerates.*
|
||||
homeassistant.components.openuv.*
|
||||
homeassistant.components.otbr.*
|
||||
homeassistant.components.overkiz.*
|
||||
homeassistant.components.peco.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
@@ -248,12 +252,14 @@ homeassistant.components.ridwell.*
|
||||
homeassistant.components.rituals_perfume_genie.*
|
||||
homeassistant.components.roku.*
|
||||
homeassistant.components.rpi_power.*
|
||||
homeassistant.components.rss_feed_template.*
|
||||
homeassistant.components.rtsp_to_webrtc.*
|
||||
homeassistant.components.ruuvi_gateway.*
|
||||
homeassistant.components.ruuvitag_ble.*
|
||||
homeassistant.components.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.sensibo.*
|
||||
|
||||
28
.vscode/tasks.json
vendored
28
.vscode/tasks.json
vendored
@@ -27,6 +27,20 @@
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pytest (changed tests only)",
|
||||
"type": "shell",
|
||||
"command": "pytest --timeout=10 --picked",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Flake8",
|
||||
"type": "shell",
|
||||
@@ -41,6 +55,20 @@
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Ruff",
|
||||
"type": "shell",
|
||||
"command": "pre-commit run ruff --all-files",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
|
||||
32
CODEOWNERS
32
CODEOWNERS
@@ -67,8 +67,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/alert/ @home-assistant/core @frenck
|
||||
/homeassistant/components/alexa/ @home-assistant/cloud @ochlocracy @jbouwh
|
||||
/tests/components/alexa/ @home-assistant/cloud @ochlocracy @jbouwh
|
||||
/homeassistant/components/almond/ @gcampax @balloob
|
||||
/tests/components/almond/ @gcampax @balloob
|
||||
/homeassistant/components/amberelectric/ @madpilot
|
||||
/tests/components/amberelectric/ @madpilot
|
||||
/homeassistant/components/ambiclimate/ @danielhiversen
|
||||
@@ -213,8 +211,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/configurator/ @home-assistant/core
|
||||
/homeassistant/components/control4/ @lawtancool
|
||||
/tests/components/control4/ @lawtancool
|
||||
/homeassistant/components/conversation/ @home-assistant/core
|
||||
/tests/components/conversation/ @home-assistant/core
|
||||
/homeassistant/components/conversation/ @home-assistant/core @synesthesiam
|
||||
/tests/components/conversation/ @home-assistant/core @synesthesiam
|
||||
/homeassistant/components/coolmaster/ @OnFreund
|
||||
/tests/components/coolmaster/ @OnFreund
|
||||
/homeassistant/components/coronavirus/ @home-assistant/core
|
||||
@@ -509,8 +507,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/homematic/ @pvizeli @danielperna84
|
||||
/homeassistant/components/homewizard/ @DCSBL
|
||||
/tests/components/homewizard/ @DCSBL
|
||||
/homeassistant/components/honeywell/ @rdfurman
|
||||
/tests/components/honeywell/ @rdfurman
|
||||
/homeassistant/components/honeywell/ @rdfurman @mkmer
|
||||
/tests/components/honeywell/ @rdfurman @mkmer
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
@@ -568,8 +566,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/integration/ @dgomes
|
||||
/homeassistant/components/intellifire/ @jeeftor
|
||||
/tests/components/intellifire/ @jeeftor
|
||||
/homeassistant/components/intent/ @home-assistant/core
|
||||
/tests/components/intent/ @home-assistant/core
|
||||
/homeassistant/components/intent/ @home-assistant/core @synesthesiam
|
||||
/tests/components/intent/ @home-assistant/core @synesthesiam
|
||||
/homeassistant/components/intesishome/ @jnimmo
|
||||
/homeassistant/components/ios/ @robbiet480
|
||||
/tests/components/ios/ @robbiet480
|
||||
@@ -740,6 +738,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/monoprice/ @etsinko @OnFreund
|
||||
/homeassistant/components/moon/ @fabaff @frenck
|
||||
/tests/components/moon/ @fabaff @frenck
|
||||
/homeassistant/components/mopeka/ @bdraco
|
||||
/tests/components/mopeka/ @bdraco
|
||||
/homeassistant/components/motion_blinds/ @starkillerOG
|
||||
/tests/components/motion_blinds/ @starkillerOG
|
||||
/homeassistant/components/motioneye/ @dermotduffy
|
||||
@@ -840,6 +840,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/onvif/ @hunterjm
|
||||
/homeassistant/components/open_meteo/ @frenck
|
||||
/tests/components/open_meteo/ @frenck
|
||||
/homeassistant/components/openai_conversation/ @balloob
|
||||
/tests/components/openai_conversation/ @balloob
|
||||
/homeassistant/components/openerz/ @misialq
|
||||
/tests/components/openerz/ @misialq
|
||||
/homeassistant/components/openexchangerates/ @MartinHjelmare
|
||||
@@ -855,8 +857,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/openweathermap/ @fabaff @freekode @nzapponi
|
||||
/homeassistant/components/opnsense/ @mtreinish
|
||||
/tests/components/opnsense/ @mtreinish
|
||||
/homeassistant/components/oralb/ @bdraco @conway20
|
||||
/tests/components/oralb/ @bdraco @conway20
|
||||
/homeassistant/components/oralb/ @bdraco @Lash-L
|
||||
/tests/components/oralb/ @bdraco @Lash-L
|
||||
/homeassistant/components/oru/ @bvlaicu
|
||||
/homeassistant/components/otbr/ @home-assistant/core
|
||||
/tests/components/otbr/ @home-assistant/core
|
||||
@@ -894,8 +896,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/point/ @fredrike
|
||||
/homeassistant/components/poolsense/ @haemishkyd
|
||||
/tests/components/poolsense/ @haemishkyd
|
||||
/homeassistant/components/powerwall/ @bdraco @jrester
|
||||
/tests/components/powerwall/ @bdraco @jrester
|
||||
/homeassistant/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/tests/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/homeassistant/components/profiler/ @bdraco
|
||||
/tests/components/profiler/ @bdraco
|
||||
/homeassistant/components/progettihwsw/ @ardaseremet
|
||||
@@ -1001,6 +1003,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ruuvi_gateway/ @akx
|
||||
/homeassistant/components/ruuvitag_ble/ @akx
|
||||
/tests/components/ruuvitag_ble/ @akx
|
||||
/homeassistant/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/tests/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/homeassistant/components/sabnzbd/ @shaiu
|
||||
/tests/components/sabnzbd/ @shaiu
|
||||
/homeassistant/components/safe_mode/ @home-assistant/core
|
||||
@@ -1139,6 +1143,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/stiebel_eltron/ @fucm
|
||||
/homeassistant/components/stookalert/ @fwestenberg @frenck
|
||||
/tests/components/stookalert/ @fwestenberg @frenck
|
||||
/homeassistant/components/stookwijzer/ @fwestenberg
|
||||
/tests/components/stookwijzer/ @fwestenberg
|
||||
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
|
||||
/tests/components/stream/ @hunterjm @uvjustin @allenporter
|
||||
/homeassistant/components/stt/ @pvizeli
|
||||
@@ -1200,6 +1206,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/thermopro/ @bdraco
|
||||
/tests/components/thermopro/ @bdraco
|
||||
/homeassistant/components/thethingsnetwork/ @fabaff
|
||||
/homeassistant/components/thread/ @home-assistant/core
|
||||
/tests/components/thread/ @home-assistant/core
|
||||
/homeassistant/components/threshold/ @fabaff
|
||||
/tests/components/threshold/ @fabaff
|
||||
/homeassistant/components/tibber/ @danielhiversen
|
||||
|
||||
@@ -5,6 +5,8 @@ FROM ${BUILD_FROM}
|
||||
ENV \
|
||||
S6_SERVICES_GRACETIME=220000
|
||||
|
||||
ARG QEMU_CPU
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
## Setup Home Assistant Core dependencies
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
|
||||
24
codecov.yml
24
codecov.yml
@@ -6,19 +6,39 @@ coverage:
|
||||
default:
|
||||
target: 90
|
||||
threshold: 0.09
|
||||
config-flows:
|
||||
required:
|
||||
target: auto
|
||||
threshold: 1
|
||||
paths:
|
||||
- homeassistant/components/*/config_flow.py
|
||||
- homeassistant/components/*/device_action.py
|
||||
- homeassistant/components/*/device_condition.py
|
||||
- homeassistant/components/*/device_trigger.py
|
||||
- homeassistant/components/*/diagnostics.py
|
||||
- homeassistant/components/*/group.py
|
||||
- homeassistant/components/*/intent.py
|
||||
- homeassistant/components/*/logbook.py
|
||||
- homeassistant/components/*/media_source.py
|
||||
- homeassistant/components/*/recorder.py
|
||||
- homeassistant/components/*/scene.py
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
config-flows:
|
||||
required:
|
||||
target: 100
|
||||
threshold: 0
|
||||
paths:
|
||||
- homeassistant/components/*/config_flow.py
|
||||
- homeassistant/components/*/device_action.py
|
||||
- homeassistant/components/*/device_condition.py
|
||||
- homeassistant/components/*/device_trigger.py
|
||||
- homeassistant/components/*/diagnostics.py
|
||||
- homeassistant/components/*/group.py
|
||||
- homeassistant/components/*/intent.py
|
||||
- homeassistant/components/*/logbook.py
|
||||
- homeassistant/components/*/media_source.py
|
||||
- homeassistant/components/*/recorder.py
|
||||
- homeassistant/components/*/scene.py
|
||||
comment: false
|
||||
|
||||
# To make partial tests possible,
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Home-Assistant documentation build configuration file, created by
|
||||
# sphinx-quickstart on Sun Aug 28 13:13:10 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
"""Home Assistant documentation build configuration file.
|
||||
|
||||
This file is execfile()d with the current directory set to its
|
||||
containing dir.
|
||||
|
||||
Note that not all possible configuration values are present in this
|
||||
autogenerated file.
|
||||
|
||||
All configuration values have a default; values that are commented out
|
||||
serve to show the default.
|
||||
|
||||
If extensions (or modules to document with autodoc) are in another directory,
|
||||
add these directories to sys.path here. If the directory is relative to the
|
||||
documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
"""
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
@@ -110,17 +109,17 @@ def linkcode_resolve(domain, info):
|
||||
for part in fullname.split("."):
|
||||
try:
|
||||
obj = getattr(obj, part)
|
||||
except:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
try:
|
||||
fn = inspect.getsourcefile(obj)
|
||||
except:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
fn = None
|
||||
if not fn:
|
||||
return None
|
||||
try:
|
||||
source, lineno = inspect.findsource(obj)
|
||||
except:
|
||||
except Exception: # pylint: disable=broad-except
|
||||
lineno = None
|
||||
if lineno:
|
||||
linespec = "#L%d" % (lineno + 1)
|
||||
|
||||
@@ -35,7 +35,7 @@ def validate_python() -> None:
|
||||
|
||||
def ensure_config_path(config_dir: str) -> None:
|
||||
"""Validate the configuration directory."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from . import config as config_util
|
||||
|
||||
lib_dir = os.path.join(config_dir, "deps")
|
||||
@@ -77,7 +77,7 @@ def ensure_config_path(config_dir: str) -> None:
|
||||
|
||||
def get_arguments() -> argparse.Namespace:
|
||||
"""Get parsed passed in arguments."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from . import config as config_util
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -184,7 +184,7 @@ def main() -> int:
|
||||
validate_os()
|
||||
|
||||
if args.script is not None:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from . import scripts
|
||||
|
||||
return scripts.run(args.script)
|
||||
@@ -192,7 +192,7 @@ def main() -> int:
|
||||
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
|
||||
ensure_config_path(config_dir)
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from . import runner
|
||||
|
||||
runtime_conf = runner.RuntimeConfig(
|
||||
|
||||
@@ -5,7 +5,7 @@ import asyncio
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Mapping
|
||||
from datetime import timedelta
|
||||
from typing import Any, Optional, cast
|
||||
from typing import Any, cast
|
||||
|
||||
import jwt
|
||||
|
||||
@@ -24,7 +24,7 @@ EVENT_USER_UPDATED = "user_updated"
|
||||
EVENT_USER_REMOVED = "user_removed"
|
||||
|
||||
_MfaModuleDict = dict[str, MultiFactorAuthModule]
|
||||
_ProviderKey = tuple[str, Optional[str]]
|
||||
_ProviderKey = tuple[str, str | None]
|
||||
_ProviderDict = dict[_ProviderKey, AuthProvider]
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
"""Common code for permissions."""
|
||||
from collections.abc import Mapping
|
||||
from typing import Union
|
||||
|
||||
# MyPy doesn't support recursion yet. So writing it out as far as we need.
|
||||
|
||||
ValueType = Union[
|
||||
ValueType = (
|
||||
# Example: entities.all = { read: true, control: true }
|
||||
Mapping[str, bool],
|
||||
bool,
|
||||
None,
|
||||
]
|
||||
Mapping[str, bool]
|
||||
| bool
|
||||
| None
|
||||
)
|
||||
|
||||
# Example: entities.domains = { light: … }
|
||||
SubCategoryDict = Mapping[str, ValueType]
|
||||
|
||||
SubCategoryType = Union[SubCategoryDict, bool, None]
|
||||
SubCategoryType = SubCategoryDict | bool | None
|
||||
|
||||
CategoryType = Union[
|
||||
CategoryType = (
|
||||
# Example: entities.domains
|
||||
Mapping[str, SubCategoryType],
|
||||
Mapping[str, SubCategoryType]
|
||||
# Example: entities.all
|
||||
Mapping[str, ValueType],
|
||||
bool,
|
||||
None,
|
||||
]
|
||||
| Mapping[str, ValueType]
|
||||
| bool
|
||||
| None
|
||||
)
|
||||
|
||||
# Example: { entities: … }
|
||||
PolicyType = Mapping[str, CategoryType]
|
||||
|
||||
@@ -3,13 +3,13 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Optional, cast
|
||||
from typing import cast
|
||||
|
||||
from .const import SUBCAT_ALL
|
||||
from .models import PermissionLookup
|
||||
from .types import CategoryType, SubCategoryDict, ValueType
|
||||
|
||||
LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], Optional[ValueType]]
|
||||
LookupFunc = Callable[[PermissionLookup, SubCategoryDict, str], ValueType | None]
|
||||
SubCatLookupType = dict[str, LookupFunc]
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from ipaddress import (
|
||||
ip_address,
|
||||
ip_network,
|
||||
)
|
||||
from typing import Any, Union, cast
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -27,8 +27,8 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
from .. import InvalidAuthError
|
||||
from ..models import Credentials, RefreshToken, UserMeta
|
||||
|
||||
IPAddress = Union[IPv4Address, IPv6Address]
|
||||
IPNetwork = Union[IPv4Network, IPv6Network]
|
||||
IPAddress = IPv4Address | IPv6Address
|
||||
IPNetwork = IPv4Network | IPv6Network
|
||||
|
||||
CONF_TRUSTED_NETWORKS = "trusted_networks"
|
||||
CONF_TRUSTED_USERS = "trusted_users"
|
||||
|
||||
@@ -346,7 +346,7 @@ def async_enable_logging(
|
||||
|
||||
if not log_no_color:
|
||||
try:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from colorlog import ColoredFormatter
|
||||
|
||||
# basicConfig must be called after importing colorlog in order to
|
||||
|
||||
@@ -3,10 +3,14 @@ from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
|
||||
from abodepy import Abode, AbodeAutomation as AbodeAuto
|
||||
from abodepy.devices import AbodeDevice as AbodeDev
|
||||
from abodepy.exceptions import AbodeAuthenticationException, AbodeException
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
from jaraco.abode.automation import Automation as AbodeAuto
|
||||
from jaraco.abode.client import Client as Abode
|
||||
from jaraco.abode.devices.base import Device as AbodeDev
|
||||
from jaraco.abode.exceptions import (
|
||||
AuthenticationException as AbodeAuthenticationException,
|
||||
Exception as AbodeException,
|
||||
)
|
||||
from jaraco.abode.helpers.timeline import Groups as GROUPS
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -26,7 +30,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, entity
|
||||
from homeassistant.helpers.dispatcher import dispatcher_send
|
||||
|
||||
from .const import ATTRIBUTION, CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
|
||||
from .const import ATTRIBUTION, CONF_POLLING, DOMAIN, LOGGER
|
||||
|
||||
SERVICE_SETTINGS = "change_setting"
|
||||
SERVICE_CAPTURE_IMAGE = "capture_image"
|
||||
@@ -82,7 +86,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
polling = entry.data[CONF_POLLING]
|
||||
cache = hass.config.path(DEFAULT_CACHEDB)
|
||||
|
||||
# For previous config entries where unique_id is None
|
||||
if entry.unique_id is None:
|
||||
@@ -92,7 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
try:
|
||||
abode = await hass.async_add_executor_job(
|
||||
Abode, username, password, True, True, True, cache
|
||||
Abode, username, password, True, True, True
|
||||
)
|
||||
|
||||
except AbodeAuthenticationException as ex:
|
||||
@@ -225,17 +228,17 @@ def setup_abode_events(hass: HomeAssistant) -> None:
|
||||
hass.bus.fire(event, data)
|
||||
|
||||
events = [
|
||||
TIMELINE.ALARM_GROUP,
|
||||
TIMELINE.ALARM_END_GROUP,
|
||||
TIMELINE.PANEL_FAULT_GROUP,
|
||||
TIMELINE.PANEL_RESTORE_GROUP,
|
||||
TIMELINE.AUTOMATION_GROUP,
|
||||
TIMELINE.DISARM_GROUP,
|
||||
TIMELINE.ARM_GROUP,
|
||||
TIMELINE.ARM_FAULT_GROUP,
|
||||
TIMELINE.TEST_GROUP,
|
||||
TIMELINE.CAPTURE_GROUP,
|
||||
TIMELINE.DEVICE_GROUP,
|
||||
GROUPS.ALARM,
|
||||
GROUPS.ALARM_END,
|
||||
GROUPS.PANEL_FAULT,
|
||||
GROUPS.PANEL_RESTORE,
|
||||
GROUPS.AUTOMATION,
|
||||
GROUPS.DISARM,
|
||||
GROUPS.ARM,
|
||||
GROUPS.ARM_FAULT,
|
||||
GROUPS.TEST,
|
||||
GROUPS.CAPTURE,
|
||||
GROUPS.DEVICE,
|
||||
]
|
||||
|
||||
for event in events:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Support for Abode Security System alarm control panels."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abodepy.devices.alarm import AbodeAlarm as AbodeAl
|
||||
from jaraco.abode.devices.alarm import Alarm as AbodeAl
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
from contextlib import suppress
|
||||
from typing import cast
|
||||
|
||||
from abodepy.devices.binary_sensor import AbodeBinarySensor as ABBinarySensor
|
||||
import abodepy.helpers.constants as CONST
|
||||
from jaraco.abode.devices.sensor import BinarySensor as ABBinarySensor
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
|
||||
@@ -4,9 +4,9 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
from typing import Any, cast
|
||||
|
||||
from abodepy.devices import CONST, AbodeDevice as AbodeDev
|
||||
from abodepy.devices.camera import AbodeCamera as AbodeCam
|
||||
import abodepy.helpers.timeline as TIMELINE
|
||||
from jaraco.abode.devices.base import Device as AbodeDev
|
||||
from jaraco.abode.devices.camera import Camera as AbodeCam
|
||||
from jaraco.abode.helpers import constants as CONST, timeline as TIMELINE
|
||||
import requests
|
||||
from requests.models import Response
|
||||
|
||||
@@ -30,7 +30,7 @@ async def async_setup_entry(
|
||||
data: AbodeSystem = hass.data[DOMAIN]
|
||||
|
||||
async_add_entities(
|
||||
AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE)
|
||||
AbodeCamera(data, device, TIMELINE.CAPTURE_IMAGE) # pylint: disable=no-member
|
||||
for device in data.abode.get_devices(generic_type=CONST.TYPE_CAMERA)
|
||||
)
|
||||
|
||||
|
||||
@@ -5,9 +5,12 @@ from collections.abc import Mapping
|
||||
from http import HTTPStatus
|
||||
from typing import Any, cast
|
||||
|
||||
from abodepy import Abode
|
||||
from abodepy.exceptions import AbodeAuthenticationException, AbodeException
|
||||
from abodepy.helpers.errors import MFA_CODE_REQUIRED
|
||||
from jaraco.abode.client import Client as Abode
|
||||
from jaraco.abode.exceptions import (
|
||||
AuthenticationException as AbodeAuthenticationException,
|
||||
Exception as AbodeException,
|
||||
)
|
||||
from jaraco.abode.helpers.errors import MFA_CODE_REQUIRED
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -15,7 +18,7 @@ from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
|
||||
from .const import CONF_POLLING, DEFAULT_CACHEDB, DOMAIN, LOGGER
|
||||
from .const import CONF_POLLING, DOMAIN, LOGGER
|
||||
|
||||
CONF_MFA = "mfa_code"
|
||||
|
||||
@@ -35,7 +38,6 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(CONF_MFA): str,
|
||||
}
|
||||
|
||||
self._cache: str | None = None
|
||||
self._mfa_code: str | None = None
|
||||
self._password: str | None = None
|
||||
self._polling: bool = False
|
||||
@@ -43,12 +45,11 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
async def _async_abode_login(self, step_id: str) -> FlowResult:
|
||||
"""Handle login with Abode."""
|
||||
self._cache = self.hass.config.path(DEFAULT_CACHEDB)
|
||||
errors = {}
|
||||
|
||||
try:
|
||||
await self.hass.async_add_executor_job(
|
||||
Abode, self._username, self._password, True, False, False, self._cache
|
||||
Abode, self._username, self._password, True, False, False
|
||||
)
|
||||
|
||||
except AbodeException as ex:
|
||||
@@ -77,12 +78,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle multi-factor authentication (MFA) login with Abode."""
|
||||
try:
|
||||
# Create instance to access login method for passing MFA code
|
||||
abode = Abode(
|
||||
auto_login=False,
|
||||
get_devices=False,
|
||||
get_automations=False,
|
||||
cache_path=self._cache,
|
||||
)
|
||||
abode = Abode(auto_login=False, get_devices=False, get_automations=False)
|
||||
await self.hass.async_add_executor_job(
|
||||
abode.login, self._username, self._password, self._mfa_code
|
||||
)
|
||||
|
||||
@@ -6,5 +6,4 @@ LOGGER = logging.getLogger(__package__)
|
||||
DOMAIN = "abode"
|
||||
ATTRIBUTION = "Data provided by goabode.com"
|
||||
|
||||
DEFAULT_CACHEDB = "abodepy_cache.pickle"
|
||||
CONF_POLLING = "polling"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Support for Abode Security System covers."""
|
||||
from typing import Any
|
||||
|
||||
from abodepy.devices.cover import AbodeCover as AbodeCV
|
||||
import abodepy.helpers.constants as CONST
|
||||
from jaraco.abode.devices.cover import Cover as AbodeCV
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.cover import CoverEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
from math import ceil
|
||||
from typing import Any
|
||||
|
||||
from abodepy.devices.light import AbodeLight as AbodeLT
|
||||
import abodepy.helpers.constants as CONST
|
||||
from jaraco.abode.devices.light import Light as AbodeLT
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Support for the Abode Security System locks."""
|
||||
from typing import Any
|
||||
|
||||
from abodepy.devices.lock import AbodeLock as AbodeLK
|
||||
import abodepy.helpers.constants as CONST
|
||||
from jaraco.abode.devices.lock import Lock as AbodeLK
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.lock import LockEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"name": "Abode",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/abode",
|
||||
"requirements": ["abodepy==1.2.0"],
|
||||
"requirements": ["jaraco.abode==3.2.1"],
|
||||
"codeowners": ["@shred86"],
|
||||
"homekit": {
|
||||
"models": ["Abode", "Iota"]
|
||||
},
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["abodepy", "lomond"]
|
||||
"loggers": ["jaraco.abode", "lomond"]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
from abodepy.devices.sensor import CONST, AbodeSensor as AbodeSense
|
||||
from jaraco.abode.devices.sensor import Sensor as AbodeSense
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
|
||||
@@ -3,7 +3,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from abodepy.devices.switch import CONST, AbodeSwitch as AbodeSW
|
||||
from jaraco.abode.devices.switch import Switch as AbodeSW
|
||||
from jaraco.abode.helpers import constants as CONST
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u0442\u0430 \u0430\u0432\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u044f \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u0430",
|
||||
"single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Abode."
|
||||
"single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
|
||||
|
||||
@@ -17,10 +17,10 @@ from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
UV_INDEX,
|
||||
UnitOfLength,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
UnitOfVolumetricFlux,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@@ -248,7 +248,8 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
metric_unit=UnitOfLength.METERS,
|
||||
us_customary_unit=UnitOfLength.FEET,
|
||||
value_fn=lambda data, unit: round(cast(float, data[unit][ATTR_VALUE])),
|
||||
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
|
||||
native_precision=0,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCover",
|
||||
@@ -290,18 +291,20 @@ SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Precipitation",
|
||||
device_class=SensorDeviceClass.PRECIPITATION,
|
||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||
name="Precipitation",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
metric_unit=UnitOfPrecipitationDepth.MILLIMETERS,
|
||||
us_customary_unit=UnitOfPrecipitationDepth.INCHES,
|
||||
metric_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||
us_customary_unit=UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
||||
value_fn=lambda data, unit: cast(float, data[unit][ATTR_VALUE]),
|
||||
attr_fn=lambda data: {"type": data["PrecipitationType"]},
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="PressureTendency",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
icon="mdi:gauge",
|
||||
name="Pressure tendency",
|
||||
options=["falling", "rising", "steady"],
|
||||
translation_key="pressure_tendency",
|
||||
value_fn=lambda data, _: cast(str, data["LocalizedText"]).lower(),
|
||||
),
|
||||
@@ -452,7 +455,7 @@ def _get_sensor_data(
|
||||
return sensors[ATTR_FORECAST][forecast_day][kind]
|
||||
|
||||
if kind == "Precipitation":
|
||||
return sensors["PrecipitationSummary"][kind]
|
||||
return sensors["PrecipitationSummary"]["PastHour"]
|
||||
|
||||
return sensors[kind]
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
},
|
||||
"step": {
|
||||
"cloud": {
|
||||
"data": {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
"""Config flow to configure Agent devices."""
|
||||
from contextlib import suppress
|
||||
|
||||
from agent import AgentConnectionError, AgentError
|
||||
from agent.a import Agent
|
||||
import voluptuous as vol
|
||||
@@ -31,10 +33,8 @@ class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
server_origin = generate_url(host, port)
|
||||
agent_client = Agent(server_origin, async_get_clientsession(self.hass))
|
||||
|
||||
try:
|
||||
with suppress(AgentConnectionError, AgentError):
|
||||
await agent_client.update()
|
||||
except (AgentConnectionError, AgentError):
|
||||
pass
|
||||
|
||||
await agent_client.close()
|
||||
|
||||
|
||||
7
homeassistant/components/agent_dvr/translations/lv.json
Normal file
7
homeassistant/components/agent_dvr/translations/lv.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -19,11 +19,10 @@ from homeassistant.const import (
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AirlyDataUpdateCoordinator
|
||||
@@ -62,7 +61,7 @@ PARALLEL_UPDATES = 1
|
||||
class AirlySensorEntityDescription(SensorEntityDescription):
|
||||
"""Class describing Airly sensor entities."""
|
||||
|
||||
value: Callable = round
|
||||
attrs: Callable[[dict[str, Any]], dict[str, Any]] = lambda data: {}
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
@@ -70,12 +69,19 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
key=ATTR_API_CAQI,
|
||||
icon="mdi:air-filter",
|
||||
name=ATTR_API_CAQI,
|
||||
native_precision=0,
|
||||
native_unit_of_measurement="CAQI",
|
||||
attrs=lambda data: {
|
||||
ATTR_LEVEL: data[ATTR_API_CAQI_LEVEL],
|
||||
ATTR_ADVICE: data[ATTR_API_ADVICE],
|
||||
ATTR_DESCRIPTION: data[ATTR_API_CAQI_DESCRIPTION],
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM1,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
name=ATTR_API_PM1,
|
||||
name="PM1.0",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -83,28 +89,39 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
key=ATTR_API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
name="PM2.5",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
name=ATTR_API_PM10,
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_HUMIDITY,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
name=ATTR_API_HUMIDITY.capitalize(),
|
||||
native_precision=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_PRESSURE,
|
||||
device_class=SensorDeviceClass.PRESSURE,
|
||||
name=ATTR_API_PRESSURE.capitalize(),
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=UnitOfPressure.HPA,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -112,36 +129,56 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
|
||||
key=ATTR_API_TEMPERATURE,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name=ATTR_API_TEMPERATURE.capitalize(),
|
||||
native_precision=1,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda value: round(value, 1),
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_CO,
|
||||
name=ATTR_API_CO,
|
||||
name="Carbon monoxide",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_CO}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_CO}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_NO2,
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
name=ATTR_API_NO2,
|
||||
name="Nitrogen dioxide",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_NO2}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_NO2}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_SO2,
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
name=ATTR_API_SO2,
|
||||
name="Sulphur dioxide",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_SO2}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_SO2}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
AirlySensorEntityDescription(
|
||||
key=ATTR_API_O3,
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
name=ATTR_API_O3,
|
||||
name="Ozone",
|
||||
native_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
attrs=lambda data: {
|
||||
ATTR_LIMIT: data[f"{ATTR_API_O3}_{SUFFIX_LIMIT}"],
|
||||
ATTR_PERCENT: round(data[f"{ATTR_API_O3}_{SUFFIX_PERCENT}"]),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -190,64 +227,15 @@ class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
|
||||
)
|
||||
self._attrs: dict[str, Any] = {}
|
||||
self._attr_native_value = coordinator.data[description.key]
|
||||
self._attr_extra_state_attributes = description.attrs(coordinator.data)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state."""
|
||||
state = self.coordinator.data[self.entity_description.key]
|
||||
return cast(StateType, self.entity_description.value(state))
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes."""
|
||||
if self.entity_description.key == ATTR_API_CAQI:
|
||||
self._attrs[ATTR_LEVEL] = self.coordinator.data[ATTR_API_CAQI_LEVEL]
|
||||
self._attrs[ATTR_ADVICE] = self.coordinator.data[ATTR_API_ADVICE]
|
||||
self._attrs[ATTR_DESCRIPTION] = self.coordinator.data[
|
||||
ATTR_API_CAQI_DESCRIPTION
|
||||
]
|
||||
if self.entity_description.key == ATTR_API_PM25:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_PM25}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_PM25}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.entity_description.key == ATTR_API_PM10:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_PM10}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_PM10}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.entity_description.key == ATTR_API_CO:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_CO}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_CO}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.entity_description.key == ATTR_API_NO2:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_NO2}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_NO2}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.entity_description.key == ATTR_API_SO2:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_SO2}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_SO2}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
if self.entity_description.key == ATTR_API_O3:
|
||||
self._attrs[ATTR_LIMIT] = self.coordinator.data[
|
||||
f"{ATTR_API_O3}_{SUFFIX_LIMIT}"
|
||||
]
|
||||
self._attrs[ATTR_PERCENT] = round(
|
||||
self.coordinator.data[f"{ATTR_API_O3}_{SUFFIX_PERCENT}"]
|
||||
)
|
||||
return self._attrs
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._attr_native_value = self.coordinator.data[self.entity_description.key]
|
||||
self._attr_extra_state_attributes = self.entity_description.attrs(
|
||||
self.coordinator.data
|
||||
)
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"data": {
|
||||
"api_key": "API \u043a\u043b\u044e\u0447 \u0437\u0430 Airly",
|
||||
"latitude": "\u0428\u0438\u0440\u0438\u043d\u0430",
|
||||
"longitude": "\u0414\u044a\u043b\u0436\u0438\u043d\u0430",
|
||||
"name": "\u0418\u043c\u0435 \u043d\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f\u0442\u0430"
|
||||
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430",
|
||||
"name": "\u0418\u043c\u0435"
|
||||
},
|
||||
"description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e\u0442\u043e \u043d\u0430 \u0432\u044a\u0437\u0434\u0443\u0445\u0430 Airly \u0417\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 \u043a\u043b\u044e\u0447 \u0437\u0430 API, \u043e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 https://developer.airly.eu/register"
|
||||
}
|
||||
|
||||
7
homeassistant/components/airnow/translations/lv.json
Normal file
7
homeassistant/components/airnow/translations/lv.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
7
homeassistant/components/airq/translations/lv.json
Normal file
7
homeassistant/components/airq/translations/lv.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
12
homeassistant/components/airq/translations/sv.json
Normal file
12
homeassistant/components/airq/translations/sv.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "IP-adress",
|
||||
"password": "L\u00f6senord"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
7
homeassistant/components/airtouch4/translations/lv.json
Normal file
7
homeassistant/components/airtouch4/translations/lv.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,8 +266,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": source},
|
||||
data={CONF_API_KEY: entry.data[CONF_API_KEY], **geography},
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={
|
||||
"import_source": source,
|
||||
CONF_API_KEY: entry.data[CONF_API_KEY],
|
||||
**geography,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -171,6 +171,13 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Define the config flow to handle options."""
|
||||
return SchemaOptionsFlowHandler(config_entry, OPTIONS_FLOW)
|
||||
|
||||
async def async_step_import(self, import_data: dict[str, str]) -> FlowResult:
|
||||
"""Handle import of config entry version 1 data."""
|
||||
import_source = import_data.pop("import_source")
|
||||
if import_source == "geography_by_coords":
|
||||
return await self.async_step_geography_by_coords(import_data)
|
||||
return await self.async_step_geography_by_name(import_data)
|
||||
|
||||
async def async_step_geography_by_coords(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> FlowResult:
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
"country": "\u0421\u0442\u0440\u0430\u043d\u0430"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u0425\u043e\u0441\u0442",
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API \u043a\u043b\u044e\u0447"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Utilitza l'API d'AirVisual per monitoritzar un/a ciutat/estat/pa\u00eds",
|
||||
"title": "Configura una ubicaci\u00f3 geogr\u00e0fica"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Amfitri\u00f3",
|
||||
"password": "Contrasenya"
|
||||
},
|
||||
"description": "Monitoritza una unitat personal d'AirVisual. Pots obtenir la contrasenya des de la interf\u00edcie d'usuari (UI) de la unitat.",
|
||||
"title": "Configuraci\u00f3 d'AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Clau API"
|
||||
|
||||
@@ -24,13 +24,6 @@
|
||||
"country": "Zem\u011b"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Hostitel",
|
||||
"password": "Heslo"
|
||||
},
|
||||
"title": "Nastaven\u00ed AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Kl\u00ed\u010d API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Verwende die AirVisual Cloud API, um ein(e) Stadt/Bundesland/Land zu \u00fcberwachen.",
|
||||
"title": "Konfiguriere einen Standort"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Passwort"
|
||||
},
|
||||
"description": "\u00dcberwache eine pers\u00f6nliche AirVisual-Einheit. Das Passwort kann von der Benutzeroberfl\u00e4che des Ger\u00e4ts abgerufen werden.",
|
||||
"title": "Konfiguriere einen AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-Schl\u00fcssel"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03bf AirVisual cloud API \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03cc\u03bb\u03b7/\u03c0\u03bf\u03bb\u03b9\u03c4\u03b5\u03af\u03b1/\u03c7\u03ce\u03c1\u03b1.",
|
||||
"title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b3\u03b5\u03c9\u03b3\u03c1\u03b1\u03c6\u03af\u03b1\u03c2"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2",
|
||||
"password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2"
|
||||
},
|
||||
"description": "\u03a0\u03b1\u03c1\u03b1\u03ba\u03bf\u03bb\u03bf\u03c5\u03b8\u03ae\u03c3\u03c4\u03b5 \u03bc\u03b9\u03b1 \u03c0\u03c1\u03bf\u03c3\u03c9\u03c0\u03b9\u03ba\u03ae \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1 AirVisual. \u039f \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03b1\u03bd\u03b1\u03ba\u03c4\u03b7\u03b8\u03b5\u03af \u03b1\u03c0\u03cc \u03c4\u03bf UI \u03c4\u03b7\u03c2 \u03bc\u03bf\u03bd\u03ac\u03b4\u03b1\u03c2.",
|
||||
"title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03bd\u03cc\u03c2 \u03ba\u03cc\u03bc\u03b2\u03bf\u03c5 AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Use the AirVisual cloud API to monitor a city/state/country.",
|
||||
"title": "Configure a Geography"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Monitor a personal AirVisual unit. The password can be retrieved from the unit's UI.",
|
||||
"title": "Configure an AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API Key"
|
||||
|
||||
@@ -22,14 +22,6 @@
|
||||
"description": "Utilice la API en la nube de AirVisual para monitorear una ciudad/estado/pa\u00eds.",
|
||||
"title": "Configurar una geograf\u00eda"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Direcci\u00f3n IP/nombre de host de la unidad",
|
||||
"password": "Contrase\u00f1a de la unidad"
|
||||
},
|
||||
"description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.",
|
||||
"title": "Configurar un AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "Vuelva a autenticar AirVisual"
|
||||
},
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Usar la API de la nube de AirVisual para supervisar una ciudad/estado/pa\u00eds.",
|
||||
"title": "Configurar una geograf\u00eda"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Contrase\u00f1a"
|
||||
},
|
||||
"description": "Supervisar una unidad AirVisual personal. La contrase\u00f1a se puede recuperar desde la IU de la unidad.",
|
||||
"title": "Configurar un AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Clave API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Kasuta AirVisual pilve API-t linna/osariigi/riigi j\u00e4lgimiseks.",
|
||||
"title": "Seadista Geography sidumine"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u00dcksuse IP-aadress / hostinimi",
|
||||
"password": "Salas\u00f5na"
|
||||
},
|
||||
"description": "J\u00e4lgige isiklikku AirVisual-seadet. Parooli saab hankida seadme kasutajaliidese kaudu.",
|
||||
"title": "Seadistage AirVisual Node / Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API v\u00f5ti"
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
"config": {
|
||||
"error": {
|
||||
"general_error": "Tapahtui tuntematon virhe."
|
||||
},
|
||||
"step": {
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"password": "Salasana"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Utilisez l'API cloud AirVisual pour surveiller une ville / un \u00e9tat / un pays.",
|
||||
"title": "Configurer un lieu g\u00e9ographique"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "H\u00f4te",
|
||||
"password": "Mot de passe"
|
||||
},
|
||||
"description": "Surveillez une unit\u00e9 personnelle AirVisual. Le mot de passe peut \u00eatre r\u00e9cup\u00e9r\u00e9 dans l'interface utilisateur de l'unit\u00e9.",
|
||||
"title": "Configurer un noeud AirVisual Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Cl\u00e9 d'API"
|
||||
|
||||
@@ -22,13 +22,6 @@
|
||||
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u05de\u05d0\u05e8\u05d7",
|
||||
"password": "\u05e1\u05d9\u05e1\u05de\u05d4"
|
||||
},
|
||||
"description": "\u05e2\u05e7\u05d5\u05d1 \u05d0\u05d7\u05e8 \u05d9\u05d7\u05d9\u05d3\u05ea AirVisual \u05d0\u05d9\u05e9\u05d9\u05ea. \u05e0\u05d9\u05ea\u05df \u05dc\u05d0\u05d7\u05d6\u05e8 \u05d0\u05ea \u05d4\u05e1\u05d9\u05e1\u05de\u05d4 \u05de\u05de\u05e9\u05e7 \u05d4\u05de\u05e9\u05ea\u05de\u05e9 \u05e9\u05dc \u05d4\u05d9\u05d7\u05d9\u05d3\u05d4."
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "\u05de\u05e4\u05ea\u05d7 API"
|
||||
|
||||
@@ -2,16 +2,6 @@
|
||||
"config": {
|
||||
"error": {
|
||||
"general_error": "\u0915\u094b\u0908 \u0905\u091c\u094d\u091e\u093e\u0924 \u0924\u094d\u0930\u0941\u091f\u093f \u0925\u0940\u0964"
|
||||
},
|
||||
"step": {
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u0907\u0915\u093e\u0908 \u0915\u0947 \u0906\u0908\u092a\u0940 \u092a\u0924\u0947/\u0939\u094b\u0938\u094d\u091f\u0928\u093e\u092e",
|
||||
"password": "\u0907\u0915\u093e\u0908 \u092a\u093e\u0938\u0935\u0930\u094d\u0921"
|
||||
},
|
||||
"description": "\u090f\u0915 \u0935\u094d\u092f\u0915\u094d\u0924\u093f\u0917\u0924 \u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0907\u0915\u093e\u0908 \u0915\u0940 \u0928\u093f\u0917\u0930\u093e\u0928\u0940 \u0915\u0930\u0947\u0902\u0964 \u092a\u093e\u0938\u0935\u0930\u094d\u0921 \u092f\u0942\u0928\u093f\u091f \u0915\u0947 \u092f\u0942\u0906\u0908 \u0938\u0947 \u092a\u094d\u0930\u093e\u092a\u094d\u0924 \u0915\u093f\u092f\u093e \u091c\u093e \u0938\u0915\u0924\u093e \u0939\u0948\u0964",
|
||||
"title": "\u090f\u092f\u0930\u0935\u093f\u091c\u0941\u0905\u0932 \u0928\u094b\u0921 \u092a\u094d\u0930\u094b"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Haszn\u00e1lja az AirVisual felh\u0151 API-t egy v\u00e1ros / \u00e1llam / orsz\u00e1g figyel\u00e9s\u00e9hez.",
|
||||
"title": "Konfigur\u00e1lja a geogr\u00e1fi\u00e1t"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "C\u00edm",
|
||||
"password": "Jelsz\u00f3"
|
||||
},
|
||||
"description": "Szem\u00e9lyes AirVisual egys\u00e9g figyel\u00e9se. A jelsz\u00f3 lek\u00e9rhet\u0151 a k\u00e9sz\u00fcl\u00e9k felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9r\u0151l.",
|
||||
"title": "AirVisual Node/Pro konfigur\u00e1l\u00e1sa"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API kulcs"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Gunakan API cloud AirVisual untuk memantau kota/negara bagian/negara.",
|
||||
"title": "Konfigurasikan Lokasi Geografi"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Kata Sandi"
|
||||
},
|
||||
"description": "Pantau unit AirVisual pribadi. Kata sandi dapat diambil dari antarmuka unit.",
|
||||
"title": "Konfigurasikan AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Kunci API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Usa l'API cloud di AirVisual per monitorare una citt\u00e0/stato/paese.",
|
||||
"title": "Configura un'area geografica"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "Monitora un'unit\u00e0 AirVisual personale. La password pu\u00f2 essere recuperata dall'interfaccia utente dell'unit\u00e0.",
|
||||
"title": "Configura un AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Chiave API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "AirVisual cloud API\u3092\u4f7f\u7528\u3057\u3066\u3001\u90fd\u5e02/\u5dde/\u56fd\u3092\u76e3\u8996\u3057\u307e\u3059\u3002",
|
||||
"title": "Geography\u306e\u8a2d\u5b9a"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u30db\u30b9\u30c8",
|
||||
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
|
||||
},
|
||||
"description": "\u500b\u4eba\u306eAirVisual\u30e6\u30cb\u30c3\u30c8\u3092\u76e3\u8996\u3057\u307e\u3059\u3002\u30d1\u30b9\u30ef\u30fc\u30c9\u306f\u3001\u672c\u4f53\u306eUI\u304b\u3089\u53d6\u5f97\u3067\u304d\u307e\u3059\u3002",
|
||||
"title": "AirVisual Node/Pro\u306e\u8a2d\u5b9a"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API\u30ad\u30fc"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "AirVisual \ud074\ub77c\uc6b0\ub4dc API\ub97c \uc0ac\uc6a9\ud558\uc5ec \ub3c4\uc2dc/\uc8fc/\uad6d\uac00\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4.",
|
||||
"title": "\uc9c0\ub9ac\uc801 \uc704\uce58 \uad6c\uc131\ud558\uae30"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\ud638\uc2a4\ud2b8",
|
||||
"password": "\ube44\ubc00\ubc88\ud638"
|
||||
},
|
||||
"description": "\uc0ac\uc6a9\uc790\uc758 AirVisual \uae30\uae30\ub97c \ubaa8\ub2c8\ud130\ub9c1\ud569\ub2c8\ub2e4. \uae30\uae30\uc758 UI \uc5d0\uc11c \ube44\ubc00\ubc88\ud638\ub97c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4.",
|
||||
"title": "AirVisual Node/Pro \uad6c\uc131\ud558\uae30"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API \ud0a4"
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
"state": "Kanton"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Passwuert"
|
||||
},
|
||||
"description": "Pers\u00e9inlech Airvisual Unit\u00e9it iwwerwaachen. Passwuert kann vum UI vum Apparat ausgelies ginn.",
|
||||
"title": "Airvisual Node/Pro ariichten"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API Schl\u00ebssel"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Gebruik de AirVisual-cloud-API om een stad/staat/land te bewaken.",
|
||||
"title": "Configureer een geografie"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Host",
|
||||
"password": "Wachtwoord"
|
||||
},
|
||||
"description": "Monitor een persoonlijke AirVisual-eenheid. Het wachtwoord kan worden opgehaald uit de gebruikersinterface van het apparaat.",
|
||||
"title": "Configureer een AirVisual Node / Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-sleutel"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Bruk AirVisual cloud API til \u00e5 overv\u00e5ke en by/stat/land.",
|
||||
"title": "Konfigurer en Geography"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Vert",
|
||||
"password": "Passord"
|
||||
},
|
||||
"description": "Overv\u00e5ke en personlig AirVisual-enhet. Passordet kan hentes fra enhetens brukergrensesnitt.",
|
||||
"title": "Konfigurer en AirVisual Node / Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-n\u00f8kkel"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "U\u017cyj API chmury AirVisual do monitorowania miasta/stanu/kraju.",
|
||||
"title": "Konfiguracja Geography"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Nazwa hosta lub adres IP",
|
||||
"password": "Has\u0142o"
|
||||
},
|
||||
"description": "Monitoruj jednostk\u0119 AirVisual. Has\u0142o mo\u017cna odzyska\u0107 z interfejsu u\u017cytkownika urz\u0105dzenia.",
|
||||
"title": "Konfiguracja AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Klucz API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Use a API de nuvem AirVisual para monitorar uma cidade/estado/pa\u00eds.",
|
||||
"title": "Configurar uma geografia"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Nome do host",
|
||||
"password": "Senha"
|
||||
},
|
||||
"description": "Monitore uma unidade AirVisual pessoal. A senha pode ser recuperada da interface do usu\u00e1rio da unidade.",
|
||||
"title": "Configurar um n\u00f3/pro AirVisual"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Chave da API"
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
"latitude": "Latitude"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Endere\u00e7o",
|
||||
"password": "Palavra-passe"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "Chave da API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "\u0414\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0433\u043e\u0440\u043e\u0434\u0430/\u0448\u0442\u0430\u0442\u0430/\u0441\u0442\u0440\u0430\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043e\u0431\u043b\u0430\u0447\u043d\u044b\u0439 API AirVisual.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u0425\u043e\u0441\u0442",
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
|
||||
},
|
||||
"description": "\u041c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.",
|
||||
"title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AirVisual Node / Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "\u041a\u043b\u044e\u0447 API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Pou\u017eite cloudov\u00e9 API AirVisual na monitorovanie mesta/\u0161t\u00e1tu/krajiny.",
|
||||
"title": "Konfigur\u00e1cia geografie"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Hostite\u013e",
|
||||
"password": "Heslo"
|
||||
},
|
||||
"description": "Monitorujte osobn\u00fa jednotku AirVisual. Heslo je mo\u017en\u00e9 z\u00edska\u0165 z pou\u017e\u00edvate\u013esk\u00e9ho rozhrania jednotky.",
|
||||
"title": "Nastavenie AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API k\u013e\u00fa\u010d"
|
||||
|
||||
@@ -8,14 +8,6 @@
|
||||
"invalid_api_key": "Vpisan neveljaven API klju\u010d"
|
||||
},
|
||||
"step": {
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "IP naslov/ime gostitelja enote",
|
||||
"password": "Geslo enote"
|
||||
},
|
||||
"description": "Spremljajte osebno napravo AirVisual. Geslo je mogo\u010de pridobiti iz uporabni\u0161kega vmesnika enote.",
|
||||
"title": "Konfigurirajte AirVisual Node/Pro"
|
||||
},
|
||||
"user": {
|
||||
"description": "Spremljajte kakovost zraka na zemljepisni lokaciji.",
|
||||
"title": "Nastavite AirVisual"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Anv\u00e4nd AirVisuals moln-API f\u00f6r att \u00f6vervaka en stad/stat/land.",
|
||||
"title": "Konfigurera en geografi"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Enhets IP-adress / v\u00e4rdnamn",
|
||||
"password": "Enhetsl\u00f6senord"
|
||||
},
|
||||
"description": "\u00d6vervaka en personlig AirVisual-enhet. L\u00f6senordet kan h\u00e4mtas fr\u00e5n enhetens anv\u00e4ndargr\u00e4nssnitt.",
|
||||
"title": "Konfigurera en AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API-nyckel"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "Bir \u015fehri/eyalet/\u00fclkeyi izlemek i\u00e7in AirVisual bulut API'sini kullan\u0131n.",
|
||||
"title": "Bir Co\u011frafyay\u0131 Yap\u0131land\u0131rma"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Sunucu",
|
||||
"password": "Parola"
|
||||
},
|
||||
"description": "Ki\u015fisel bir AirVisual \u00fcnitesini izleyin. Parola, \u00fcnitenin kullan\u0131c\u0131 aray\u00fcz\u00fcnden al\u0131nabilir.",
|
||||
"title": "Bir AirVisual Node/Pro'yu yap\u0131land\u0131r\u0131n"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API Anahtar\u0131"
|
||||
|
||||
@@ -10,14 +10,6 @@
|
||||
"invalid_api_key": "\u0425\u0438\u0431\u043d\u0438\u0439 \u043a\u043b\u044e\u0447 API"
|
||||
},
|
||||
"step": {
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u0425\u043e\u0441\u0442",
|
||||
"password": "\u041f\u0430\u0440\u043e\u043b\u044c"
|
||||
},
|
||||
"description": "\u041c\u043e\u043d\u0456\u0442\u043e\u0440\u0438\u043d\u0433 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u044e AirVisual. \u041f\u0430\u0440\u043e\u043b\u044c \u043c\u043e\u0436\u043d\u0430 \u043e\u0442\u0440\u0438\u043c\u0430\u0442\u0438 \u0432 \u0456\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0456 \u043f\u0440\u0438\u0441\u0442\u0440\u043e\u0457\u0432.",
|
||||
"title": "\u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f AirVisual Node / Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "\u041a\u043b\u044e\u0447 API"
|
||||
|
||||
@@ -30,14 +30,6 @@
|
||||
"description": "\u4f7f\u7528 AirVisual \u96f2\u7aef API \u4ee5\u76e3\u63a7\u57ce\u5e02/\u5dde/\u570b\u5bb6\u3002",
|
||||
"title": "\u8a2d\u5b9a\u5730\u7406\u5ea7\u6a19"
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "\u4e3b\u6a5f\u7aef",
|
||||
"password": "\u5bc6\u78bc"
|
||||
},
|
||||
"description": "\u76e3\u63a7\u500b\u4eba AirVisual \u88dd\u7f6e\uff0c\u5bc6\u78bc\u53ef\u4ee5\u900f\u904e\u88dd\u7f6e UI \u7372\u5f97\u3002",
|
||||
"title": "\u8a2d\u5b9a AirVisual Node/Pro"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "API \u91d1\u9470"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,5 +152,5 @@ class AirzoneZoneEntity(AirzoneEntity):
|
||||
raise HomeAssistantError(
|
||||
f"Failed to set zone {self.name}: {error}"
|
||||
) from error
|
||||
else:
|
||||
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
|
||||
|
||||
self.coordinator.async_set_updated_data(self.coordinator.airzone.data())
|
||||
|
||||
7
homeassistant/components/airzone/translations/lv.json
Normal file
7
homeassistant/components/airzone/translations/lv.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "aladdin_connect",
|
||||
"name": "Aladdin Connect",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"requirements": ["AIOAladdinConnect==0.1.52"],
|
||||
"requirements": ["AIOAladdinConnect==0.1.54"],
|
||||
"codeowners": ["@mkmer"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aladdin_connect"],
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ier\u012bce jau pievienota Home Assistant."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
"abort": {
|
||||
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0430\u043d \u0441 AlarmDecoder."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
|
||||
},
|
||||
@@ -19,5 +22,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"arm_settings": {
|
||||
"data": {
|
||||
"code_arm_required": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u0435 \u043a\u043e\u0434 \u0437\u0430 \u043f\u043e\u0435\u043c\u0430\u043d\u0435 \u043f\u043e\u0434 \u043e\u0445\u0440\u0430\u043d\u0430"
|
||||
}
|
||||
},
|
||||
"init": {
|
||||
"data": {
|
||||
"edit_select": "\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0430\u043d\u0435"
|
||||
}
|
||||
},
|
||||
"zone_details": {
|
||||
"data": {
|
||||
"zone_name": "\u0418\u043c\u0435 \u043d\u0430 \u0437\u043e\u043d\u0430",
|
||||
"zone_type": "\u0422\u0438\u043f \u0437\u043e\u043d\u0430"
|
||||
}
|
||||
},
|
||||
"zone_select": {
|
||||
"data": {
|
||||
"zone_number": "\u041d\u043e\u043c\u0435\u0440 \u043d\u0430 \u0437\u043e\u043d\u0430"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ from homeassistant.components import (
|
||||
input_number,
|
||||
light,
|
||||
media_player,
|
||||
number,
|
||||
timer,
|
||||
vacuum,
|
||||
)
|
||||
@@ -26,6 +27,7 @@ from homeassistant.const import (
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_TEMPERATURE,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
PERCENTAGE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
@@ -41,6 +43,10 @@ from homeassistant.const import (
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNLOCKED,
|
||||
STATE_UNLOCKING,
|
||||
UnitOfLength,
|
||||
UnitOfMass,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolume,
|
||||
)
|
||||
from homeassistant.core import State
|
||||
import homeassistant.util.color as color_util
|
||||
@@ -65,6 +71,34 @@ from .resources import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
UNIT_TO_CATALOG_TAG = {
|
||||
UnitOfTemperature.CELSIUS: AlexaGlobalCatalog.UNIT_TEMPERATURE_CELSIUS,
|
||||
UnitOfTemperature.FAHRENHEIT: AlexaGlobalCatalog.UNIT_TEMPERATURE_FAHRENHEIT,
|
||||
UnitOfTemperature.KELVIN: AlexaGlobalCatalog.UNIT_TEMPERATURE_KELVIN,
|
||||
UnitOfLength.METERS: AlexaGlobalCatalog.UNIT_DISTANCE_METERS,
|
||||
UnitOfLength.KILOMETERS: AlexaGlobalCatalog.UNIT_DISTANCE_KILOMETERS,
|
||||
UnitOfLength.INCHES: AlexaGlobalCatalog.UNIT_DISTANCE_INCHES,
|
||||
UnitOfLength.FEET: AlexaGlobalCatalog.UNIT_DISTANCE_FEET,
|
||||
UnitOfLength.YARDS: AlexaGlobalCatalog.UNIT_DISTANCE_YARDS,
|
||||
UnitOfLength.MILES: AlexaGlobalCatalog.UNIT_DISTANCE_MILES,
|
||||
UnitOfMass.GRAMS: AlexaGlobalCatalog.UNIT_MASS_GRAMS,
|
||||
UnitOfMass.KILOGRAMS: AlexaGlobalCatalog.UNIT_MASS_KILOGRAMS,
|
||||
UnitOfMass.POUNDS: AlexaGlobalCatalog.UNIT_WEIGHT_POUNDS,
|
||||
UnitOfMass.OUNCES: AlexaGlobalCatalog.UNIT_WEIGHT_OUNCES,
|
||||
UnitOfVolume.LITERS: AlexaGlobalCatalog.UNIT_VOLUME_LITERS,
|
||||
UnitOfVolume.CUBIC_FEET: AlexaGlobalCatalog.UNIT_VOLUME_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS: AlexaGlobalCatalog.UNIT_VOLUME_CUBIC_METERS,
|
||||
UnitOfVolume.GALLONS: AlexaGlobalCatalog.UNIT_VOLUME_GALLONS,
|
||||
PERCENTAGE: AlexaGlobalCatalog.UNIT_PERCENT,
|
||||
"preset": AlexaGlobalCatalog.SETTING_PRESET,
|
||||
}
|
||||
|
||||
|
||||
def get_resource_by_unit_of_measurement(entity: State) -> str:
|
||||
"""Translate the unit of measurement to an Alexa Global Catalog keyword."""
|
||||
unit: str = entity.attributes.get("unit_of_measurement", "preset")
|
||||
return UNIT_TO_CATALOG_TAG.get(unit, AlexaGlobalCatalog.SETTING_PRESET)
|
||||
|
||||
|
||||
class AlexaCapability:
|
||||
"""Base class for Alexa capability interfaces.
|
||||
@@ -78,10 +112,16 @@ class AlexaCapability:
|
||||
|
||||
supported_locales = {"en-US"}
|
||||
|
||||
def __init__(self, entity: State, instance: str | None = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
entity: State,
|
||||
instance: str | None = None,
|
||||
non_controllable_properties: bool | None = None,
|
||||
) -> None:
|
||||
"""Initialize an Alexa capability."""
|
||||
self.entity = entity
|
||||
self.instance = instance
|
||||
self._non_controllable_properties = non_controllable_properties
|
||||
|
||||
def name(self) -> str:
|
||||
"""Return the Alexa API name of this interface."""
|
||||
@@ -101,7 +141,7 @@ class AlexaCapability:
|
||||
|
||||
def properties_non_controllable(self) -> bool | None:
|
||||
"""Return True if non controllable."""
|
||||
return None
|
||||
return self._non_controllable_properties
|
||||
|
||||
def get_property(self, name):
|
||||
"""Read and return a property.
|
||||
@@ -135,16 +175,16 @@ class AlexaCapability:
|
||||
def configuration(self):
|
||||
"""Return the configuration object.
|
||||
|
||||
Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController,
|
||||
and EventDetectionSensor.
|
||||
Applicable to the ThermostatController, SecurityControlPanel, ModeController,
|
||||
RangeController, and EventDetectionSensor.
|
||||
"""
|
||||
return []
|
||||
|
||||
def configurations(self):
|
||||
"""Return the configurations object.
|
||||
|
||||
The plural configurations object is different that the singular configuration object.
|
||||
Applicable to EqualizerController interface.
|
||||
The plural configurations object is different that the singular configuration
|
||||
object. Applicable to EqualizerController interface.
|
||||
"""
|
||||
return []
|
||||
|
||||
@@ -196,7 +236,8 @@ class AlexaCapability:
|
||||
if configuration := self.configuration():
|
||||
result["configuration"] = configuration
|
||||
|
||||
# The plural configurations object is different than the singular configuration object above.
|
||||
# The plural configurations object is different than the singular
|
||||
# configuration object above.
|
||||
if configurations := self.configurations():
|
||||
result["configurations"] = configurations
|
||||
|
||||
@@ -757,7 +798,8 @@ class AlexaPlaybackController(AlexaCapability):
|
||||
def supported_operations(self):
|
||||
"""Return the supportedOperations object.
|
||||
|
||||
Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind, StartOver, Stop
|
||||
Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind,
|
||||
StartOver, Stop
|
||||
"""
|
||||
supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
|
||||
@@ -1117,7 +1159,9 @@ class AlexaThermostatController(AlexaCapability):
|
||||
def configuration(self):
|
||||
"""Return configuration object.
|
||||
|
||||
Translates climate HVAC_MODES and PRESETS to supported Alexa ThermostatMode Values.
|
||||
Translates climate HVAC_MODES and PRESETS to supported Alexa
|
||||
ThermostatMode Values.
|
||||
|
||||
ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM.
|
||||
"""
|
||||
supported_modes = []
|
||||
@@ -1133,7 +1177,8 @@ class AlexaThermostatController(AlexaCapability):
|
||||
if thermostat_mode:
|
||||
supported_modes.append(thermostat_mode)
|
||||
|
||||
# Return False for supportsScheduling until supported with event listener in handler.
|
||||
# Return False for supportsScheduling until supported with event
|
||||
# listener in handler.
|
||||
configuration = {"supportsScheduling": False}
|
||||
|
||||
if supported_modes:
|
||||
@@ -1270,12 +1315,15 @@ class AlexaSecurityPanelController(AlexaCapability):
|
||||
class AlexaModeController(AlexaCapability):
|
||||
"""Implements Alexa.ModeController.
|
||||
|
||||
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
|
||||
The instance property should be a concatenated string of device domain period and single word.
|
||||
e.g. fan.speed & fan.direction.
|
||||
The instance property must be unique across ModeController, RangeController,
|
||||
ToggleController within the same device.
|
||||
|
||||
The instance property must not contain words from other instance property strings within the same device.
|
||||
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
The instance property should be a concatenated string of device domain period
|
||||
and single word. e.g. fan.speed & fan.direction.
|
||||
|
||||
The instance property must not contain words from other instance property
|
||||
strings within the same device. e.g. Instance property cover.position &
|
||||
cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
|
||||
An instance property string value may be reused for different devices.
|
||||
|
||||
@@ -1302,10 +1350,9 @@ class AlexaModeController(AlexaCapability):
|
||||
|
||||
def __init__(self, entity, instance, non_controllable=False):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(entity, instance)
|
||||
AlexaCapability.__init__(self, entity, instance, non_controllable)
|
||||
self._resource = None
|
||||
self._semantics = None
|
||||
self.properties_non_controllable = lambda: non_controllable
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
@@ -1408,8 +1455,8 @@ class AlexaModeController(AlexaCapability):
|
||||
modes = self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, [])
|
||||
for mode in modes:
|
||||
self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
|
||||
# Humidifiers or Fans with a single mode completely break Alexa discovery, add a
|
||||
# fake preset (see issue #53832).
|
||||
# Humidifiers or Fans with a single mode completely break Alexa discovery,
|
||||
# add a fake preset (see issue #53832).
|
||||
if len(modes) == 1:
|
||||
self._resource.add_mode(
|
||||
f"{humidifier.ATTR_MODE}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
|
||||
@@ -1479,12 +1526,15 @@ class AlexaModeController(AlexaCapability):
|
||||
class AlexaRangeController(AlexaCapability):
|
||||
"""Implements Alexa.RangeController.
|
||||
|
||||
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
|
||||
The instance property should be a concatenated string of device domain period and single word.
|
||||
e.g. fan.speed & fan.direction.
|
||||
The instance property must be unique across ModeController, RangeController,
|
||||
ToggleController within the same device.
|
||||
|
||||
The instance property must not contain words from other instance property strings within the same device.
|
||||
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
The instance property should be a concatenated string of device domain period
|
||||
and single word. e.g. fan.speed & fan.direction.
|
||||
|
||||
The instance property must not contain words from other instance property
|
||||
strings within the same device. e.g. Instance property cover.position &
|
||||
cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
|
||||
An instance property string value may be reused for different devices.
|
||||
|
||||
@@ -1509,12 +1559,13 @@ class AlexaRangeController(AlexaCapability):
|
||||
"pt-BR",
|
||||
}
|
||||
|
||||
def __init__(self, entity, instance, non_controllable=False):
|
||||
def __init__(
|
||||
self, entity: State, instance: str | None, non_controllable: bool = False
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(entity, instance)
|
||||
AlexaCapability.__init__(self, entity, instance, non_controllable)
|
||||
self._resource = None
|
||||
self._semantics = None
|
||||
self.properties_non_controllable = lambda: non_controllable
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
@@ -1538,7 +1589,8 @@ class AlexaRangeController(AlexaCapability):
|
||||
raise UnsupportedProperty(name)
|
||||
|
||||
# Return None for unavailable and unknown states.
|
||||
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
|
||||
# Allows the Alexa.EndpointHealth Interface to handle the unavailable
|
||||
# state in a stateReport.
|
||||
if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
|
||||
return None
|
||||
|
||||
@@ -1567,6 +1619,10 @@ class AlexaRangeController(AlexaCapability):
|
||||
if self.instance == f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}":
|
||||
return float(self.entity.state)
|
||||
|
||||
# Number Value
|
||||
if self.instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
|
||||
return float(self.entity.state)
|
||||
|
||||
# Vacuum Fan Speed
|
||||
if self.instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
|
||||
speed_list = self.entity.attributes.get(vacuum.ATTR_FAN_SPEED_LIST)
|
||||
@@ -1644,7 +1700,29 @@ class AlexaRangeController(AlexaCapability):
|
||||
unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
self._resource = AlexaPresetResource(
|
||||
["Value", AlexaGlobalCatalog.SETTING_PRESET],
|
||||
["Value", get_resource_by_unit_of_measurement(self.entity)],
|
||||
min_value=min_value,
|
||||
max_value=max_value,
|
||||
precision=precision,
|
||||
unit=unit,
|
||||
)
|
||||
self._resource.add_preset(
|
||||
value=min_value, labels=[AlexaGlobalCatalog.VALUE_MINIMUM]
|
||||
)
|
||||
self._resource.add_preset(
|
||||
value=max_value, labels=[AlexaGlobalCatalog.VALUE_MAXIMUM]
|
||||
)
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Number Value
|
||||
if self.instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
|
||||
min_value = float(self.entity.attributes[number.ATTR_MIN])
|
||||
max_value = float(self.entity.attributes[number.ATTR_MAX])
|
||||
precision = float(self.entity.attributes.get(number.ATTR_STEP, 1))
|
||||
unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
self._resource = AlexaPresetResource(
|
||||
["Value", get_resource_by_unit_of_measurement(self.entity)],
|
||||
min_value=min_value,
|
||||
max_value=max_value,
|
||||
precision=precision,
|
||||
@@ -1760,12 +1838,15 @@ class AlexaRangeController(AlexaCapability):
|
||||
class AlexaToggleController(AlexaCapability):
|
||||
"""Implements Alexa.ToggleController.
|
||||
|
||||
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
|
||||
The instance property should be a concatenated string of device domain period and single word.
|
||||
e.g. fan.speed & fan.direction.
|
||||
The instance property must be unique across ModeController, RangeController,
|
||||
ToggleController within the same device.
|
||||
|
||||
The instance property must not contain words from other instance property strings within the same device.
|
||||
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
The instance property should be a concatenated string of device domain period
|
||||
and single word. e.g. fan.speed & fan.direction.
|
||||
|
||||
The instance property must not contain words from other instance property
|
||||
strings within the same device. e.g. Instance property cover.position
|
||||
& cover.tilt_position will cause the Alexa.Discovery directive to fail.
|
||||
|
||||
An instance property string value may be reused for different devices.
|
||||
|
||||
@@ -1792,10 +1873,9 @@ class AlexaToggleController(AlexaCapability):
|
||||
|
||||
def __init__(self, entity, instance, non_controllable=False):
|
||||
"""Initialize the entity."""
|
||||
super().__init__(entity, instance)
|
||||
AlexaCapability.__init__(self, entity, instance, non_controllable)
|
||||
self._resource = None
|
||||
self._semantics = None
|
||||
self.properties_non_controllable = lambda: non_controllable
|
||||
|
||||
def name(self):
|
||||
"""Return the Alexa API name of this interface."""
|
||||
@@ -2021,7 +2101,8 @@ class AlexaEventDetectionSensor(AlexaCapability):
|
||||
state = self.entity.state
|
||||
|
||||
# Return None for unavailable and unknown states.
|
||||
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
|
||||
# Allows the Alexa.EndpointHealth Interface to handle the unavailable
|
||||
# state in a stateReport.
|
||||
if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
|
||||
return None
|
||||
|
||||
@@ -2089,7 +2170,8 @@ class AlexaEqualizerController(AlexaCapability):
|
||||
def properties_supported(self):
|
||||
"""Return what properties this entity supports.
|
||||
|
||||
Either bands, mode or both can be specified. Only mode is supported at this time.
|
||||
Either bands, mode or both can be specified. Only mode is supported
|
||||
at this time.
|
||||
"""
|
||||
return [{"name": "mode"}]
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Config helpers for Alexa."""
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import CALLBACK_TYPE, callback
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -16,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class AbstractConfig(ABC):
|
||||
"""Hold the configuration for Alexa."""
|
||||
|
||||
_unsub_proactive_report = None
|
||||
_unsub_proactive_report: asyncio.Task[CALLBACK_TYPE] | None = None
|
||||
|
||||
def __init__(self, hass):
|
||||
"""Initialize abstract config."""
|
||||
|
||||
@@ -23,6 +23,7 @@ from homeassistant.components import (
|
||||
light,
|
||||
lock,
|
||||
media_player,
|
||||
number,
|
||||
scene,
|
||||
script,
|
||||
sensor,
|
||||
@@ -103,7 +104,8 @@ class DisplayCategory:
|
||||
# Indicates a device that cools the air in interior spaces.
|
||||
AIR_CONDITIONER = "AIR_CONDITIONER"
|
||||
|
||||
# Indicates a device that emits pleasant odors and masks unpleasant odors in interior spaces.
|
||||
# Indicates a device that emits pleasant odors and masks unpleasant
|
||||
# odors in interior spaces.
|
||||
AIR_FRESHENER = "AIR_FRESHENER"
|
||||
|
||||
# Indicates a device that improves the quality of air in interior spaces.
|
||||
@@ -143,7 +145,8 @@ class DisplayCategory:
|
||||
GAME_CONSOLE = "GAME_CONSOLE"
|
||||
|
||||
# Indicates a garage door.
|
||||
# Garage doors must implement the ModeController interface to open and close the door.
|
||||
# Garage doors must implement the ModeController interface to
|
||||
# open and close the door.
|
||||
GARAGE_DOOR = "GARAGE_DOOR"
|
||||
|
||||
# Indicates a wearable device that transmits audio directly into the ear.
|
||||
@@ -206,8 +209,8 @@ class DisplayCategory:
|
||||
# Indicates a security system.
|
||||
SECURITY_SYSTEM = "SECURITY_SYSTEM"
|
||||
|
||||
# Indicates an electric cooking device that sits on a countertop, cooks at low temperatures,
|
||||
# and is often shaped like a cooking pot.
|
||||
# Indicates an electric cooking device that sits on a countertop,
|
||||
# cooks at low temperatures, and is often shaped like a cooking pot.
|
||||
SLOW_COOKER = "SLOW_COOKER"
|
||||
|
||||
# Indicates an endpoint that locks.
|
||||
@@ -243,7 +246,8 @@ class DisplayCategory:
|
||||
# Indicates a vacuum cleaner.
|
||||
VACUUM_CLEANER = "VACUUM_CLEANER"
|
||||
|
||||
# Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear.
|
||||
# Indicates a network-connected wearable device, such as an Apple Watch,
|
||||
# Fitbit, or Samsung Gear.
|
||||
WEARABLE = "WEARABLE"
|
||||
|
||||
|
||||
@@ -574,9 +578,10 @@ class FanCapabilities(AlexaEntity):
|
||||
force_range_controller = False
|
||||
|
||||
# AlexaRangeController controls the Fan Speed Percentage.
|
||||
# For fans which only support on/off, no controller is added. This makes the
|
||||
# fan impossible to turn on or off through Alexa, most likely due to a bug in Alexa.
|
||||
# As a workaround, we add a range controller which can only be set to 0% or 100%.
|
||||
# For fans which only support on/off, no controller is added. This makes
|
||||
# the fan impossible to turn on or off through Alexa, most likely due
|
||||
# to a bug in Alexa. As a workaround, we add a range controller which
|
||||
# can only be set to 0% or 100%.
|
||||
if force_range_controller or supported & fan.FanEntityFeature.SET_SPEED:
|
||||
yield AlexaRangeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}"
|
||||
@@ -849,8 +854,9 @@ class ImageProcessingCapabilities(AlexaEntity):
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(input_number.DOMAIN)
|
||||
@ENTITY_ADAPTERS.register(number.DOMAIN)
|
||||
class InputNumberCapabilities(AlexaEntity):
|
||||
"""Class to represent input_number capabilities."""
|
||||
"""Class to represent number and input_number capabilities."""
|
||||
|
||||
def default_display_categories(self):
|
||||
"""Return the display categories for this entity."""
|
||||
@@ -858,10 +864,8 @@ class InputNumberCapabilities(AlexaEntity):
|
||||
|
||||
def interfaces(self):
|
||||
"""Yield the supported interfaces."""
|
||||
|
||||
yield AlexaRangeController(
|
||||
self.entity, instance=f"{input_number.DOMAIN}.{input_number.ATTR_VALUE}"
|
||||
)
|
||||
domain = self.entity.domain
|
||||
yield AlexaRangeController(self.entity, instance=f"{domain}.value")
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.hass)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.components import (
|
||||
input_number,
|
||||
light,
|
||||
media_player,
|
||||
number,
|
||||
timer,
|
||||
vacuum,
|
||||
)
|
||||
@@ -613,9 +614,10 @@ async def async_api_adjust_volume_step(
|
||||
"""Process an adjust volume step request."""
|
||||
# media_player volume up/down service does not support specifying steps
|
||||
# each component handles it differently e.g. via config.
|
||||
# This workaround will simply call the volume up/Volume down the amount of steps asked for
|
||||
# When no steps are called in the request, Alexa sends a default of 10 steps which for most
|
||||
# purposes is too high. The default is set 1 in this case.
|
||||
# This workaround will simply call the volume up/Volume down the amount of
|
||||
# steps asked for. When no steps are called in the request, Alexa sends
|
||||
# a default of 10 steps which for most purposes is too high. The default
|
||||
# is set 1 in this case.
|
||||
entity = directive.entity
|
||||
volume_int = int(directive.payload["volumeSteps"])
|
||||
is_default = bool(directive.payload["volumeStepsDefault"])
|
||||
@@ -1020,8 +1022,9 @@ async def async_api_disarm(
|
||||
data = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
response = directive.response()
|
||||
|
||||
# Per Alexa Documentation: If you receive a Disarm directive, and the system is already disarmed,
|
||||
# respond with a success response, not an error response.
|
||||
# Per Alexa Documentation: If you receive a Disarm directive, and the
|
||||
# system is already disarmed, respond with a success response,
|
||||
# not an error response.
|
||||
if entity.state == STATE_ALARM_DISARMED:
|
||||
return response
|
||||
|
||||
@@ -1136,7 +1139,8 @@ async def async_api_adjust_mode(
|
||||
Only supportedModes with ordered=True support the adjustMode directive.
|
||||
"""
|
||||
|
||||
# Currently no supportedModes are configured with ordered=True to support this request.
|
||||
# Currently no supportedModes are configured with ordered=True
|
||||
# to support this request.
|
||||
raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
|
||||
|
||||
|
||||
@@ -1282,6 +1286,14 @@ async def async_api_set_range(
|
||||
max_value = float(entity.attributes[input_number.ATTR_MAX])
|
||||
data[input_number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
|
||||
|
||||
# Input Number Value
|
||||
elif instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
|
||||
range_value = float(range_value)
|
||||
service = number.SERVICE_SET_VALUE
|
||||
min_value = float(entity.attributes[number.ATTR_MIN])
|
||||
max_value = float(entity.attributes[number.ATTR_MAX])
|
||||
data[number.ATTR_VALUE] = min(max_value, max(min_value, range_value))
|
||||
|
||||
# Vacuum Fan Speed
|
||||
elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
|
||||
service = vacuum.SERVICE_SET_FAN_SPEED
|
||||
@@ -1413,6 +1425,17 @@ async def async_api_adjust_range(
|
||||
max_value, max(min_value, range_delta + current)
|
||||
)
|
||||
|
||||
# Number Value
|
||||
elif instance == f"{number.DOMAIN}.{number.ATTR_VALUE}":
|
||||
range_delta = float(range_delta)
|
||||
service = number.SERVICE_SET_VALUE
|
||||
min_value = float(entity.attributes[number.ATTR_MIN])
|
||||
max_value = float(entity.attributes[number.ATTR_MAX])
|
||||
current = float(entity.state)
|
||||
data[number.ATTR_VALUE] = response_value = min(
|
||||
max_value, max(min_value, range_delta + current)
|
||||
)
|
||||
|
||||
# Vacuum Fan Speed
|
||||
elif instance == f"{vacuum.DOMAIN}.{vacuum.ATTR_FAN_SPEED}":
|
||||
range_delta = int(range_delta)
|
||||
@@ -1483,7 +1506,9 @@ async def async_api_changechannel(
|
||||
data = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_MEDIA_CONTENT_ID: channel,
|
||||
media_player.const.ATTR_MEDIA_CONTENT_TYPE: media_player.const.MEDIA_TYPE_CHANNEL,
|
||||
media_player.const.ATTR_MEDIA_CONTENT_TYPE: (
|
||||
media_player.const.MEDIA_TYPE_CHANNEL
|
||||
),
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
|
||||
@@ -6,12 +6,15 @@ class AlexaGlobalCatalog:
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog
|
||||
|
||||
You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units.
|
||||
This catalog is localized into all the languages that Alexa supports.
|
||||
You can use the global Alexa catalog for pre-defined names of devices, settings,
|
||||
values, and units.
|
||||
|
||||
This catalog is localized into all the languages that Alexa supports.
|
||||
You can reference the following catalog of pre-defined friendly names.
|
||||
Each item in the following list is an asset identifier followed by its supported friendly names.
|
||||
The first friendly name for each identifier is the one displayed in the Alexa mobile app.
|
||||
|
||||
Each item in the following list is an asset identifier followed by its
|
||||
supported friendly names. The first friendly name for each identifier is
|
||||
the one displayed in the Alexa mobile app.
|
||||
"""
|
||||
|
||||
# Air Purifier, Air Cleaner,Clean Air Machine
|
||||
@@ -23,7 +26,8 @@ class AlexaGlobalCatalog:
|
||||
# Router, Internet Router, Network Router, Wifi Router, Net Router
|
||||
DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router"
|
||||
|
||||
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind
|
||||
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning,
|
||||
# Window shade, Interior blind
|
||||
DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade"
|
||||
|
||||
# Shower
|
||||
@@ -190,10 +194,13 @@ class AlexaGlobalCatalog:
|
||||
|
||||
|
||||
class AlexaCapabilityResource:
|
||||
"""Base class for Alexa capabilityResources, modeResources, and presetResources objects.
|
||||
"""Base class for Alexa capabilityResources, modeResources, and presetResources.
|
||||
|
||||
Resources objects labels must be unique across all modeResources and
|
||||
presetResources within the same device. To provide support for all
|
||||
supported locales, include one label from the AlexaGlobalCatalog in the
|
||||
labels array.
|
||||
|
||||
Resources objects labels must be unique across all modeResources and presetResources within the same device.
|
||||
To provide support for all supported locales, include one label from the AlexaGlobalCatalog in the labels array.
|
||||
You cannot use any words from the following list as friendly names:
|
||||
https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#names-you-cannot-use
|
||||
|
||||
@@ -211,11 +218,17 @@ class AlexaCapabilityResource:
|
||||
return self.serialize_labels(self._resource_labels)
|
||||
|
||||
def serialize_configuration(self):
|
||||
"""Return ModeResources, PresetResources friendlyNames serialized for an API response."""
|
||||
"""Return serialized configuration for an API response.
|
||||
|
||||
Return ModeResources, PresetResources friendlyNames serialized.
|
||||
"""
|
||||
return []
|
||||
|
||||
def serialize_labels(self, resources):
|
||||
"""Return resource label objects for friendlyNames serialized for an API response."""
|
||||
"""Return serialized labels for an API response.
|
||||
|
||||
Returns resource label objects for friendlyNames serialized.
|
||||
"""
|
||||
labels = []
|
||||
for label in resources:
|
||||
if label in AlexaGlobalCatalog.__dict__.values():
|
||||
@@ -245,7 +258,10 @@ class AlexaModeResource(AlexaCapabilityResource):
|
||||
self._supported_modes.append({"value": value, "labels": labels})
|
||||
|
||||
def serialize_configuration(self):
|
||||
"""Return configuration for ModeResources friendlyNames serialized for an API response."""
|
||||
"""Return serialized configuration for an API response.
|
||||
|
||||
Returns configuration for ModeResources friendlyNames serialized.
|
||||
"""
|
||||
mode_resources = []
|
||||
for mode in self._supported_modes:
|
||||
result = {
|
||||
@@ -260,7 +276,8 @@ class AlexaModeResource(AlexaCapabilityResource):
|
||||
class AlexaPresetResource(AlexaCapabilityResource):
|
||||
"""Implements Alexa PresetResources.
|
||||
|
||||
Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset.
|
||||
Use presetResources with RangeController to provide a set of
|
||||
friendlyNamesfor each RangeController preset.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
|
||||
"""
|
||||
@@ -281,7 +298,10 @@ class AlexaPresetResource(AlexaCapabilityResource):
|
||||
self._presets.append({"value": value, "labels": labels})
|
||||
|
||||
def serialize_configuration(self):
|
||||
"""Return configuration for PresetResources friendlyNames serialized for an API response."""
|
||||
"""Return serialized configuration for an API response.
|
||||
|
||||
Returns configuration for PresetResources friendlyNames serialized.
|
||||
"""
|
||||
configuration = {
|
||||
"supportedRange": {
|
||||
"minimumValue": self._minimum_value,
|
||||
@@ -309,18 +329,23 @@ class AlexaPresetResource(AlexaCapabilityResource):
|
||||
class AlexaSemantics:
|
||||
"""Class for Alexa Semantics Object.
|
||||
|
||||
You can optionally enable additional utterances by using semantics. When you use semantics,
|
||||
you manually map the phrases "open", "close", "raise", and "lower" to directives.
|
||||
You can optionally enable additional utterances by using semantics. When
|
||||
you use semantics, you manually map the phrases "open", "close", "raise",
|
||||
and "lower" to directives.
|
||||
|
||||
Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController.
|
||||
Semantics is supported for the following interfaces only: ModeController,
|
||||
RangeController, and ToggleController.
|
||||
|
||||
Semantics stateMappings are only supported for one interface of the same type on the same device. If a device has
|
||||
multiple RangeControllers only one interface may use stateMappings otherwise discovery will fail.
|
||||
Semantics stateMappings are only supported for one interface of the same
|
||||
type on the same device. If a device has multiple RangeControllers only
|
||||
one interface may use stateMappings otherwise discovery will fail.
|
||||
|
||||
You can support semantics actionMappings on different controllers for the same device, however each controller must
|
||||
support different phrases. For example, you can support "raise" on a RangeController, and "open" on a ModeController,
|
||||
but you can't support "open" on both RangeController and ModeController. Semantics stateMappings are only supported
|
||||
for one interface on the same device.
|
||||
You can support semantics actionMappings on different controllers for the
|
||||
same device, however each controller must support different phrases.
|
||||
For example, you can support "raise" on a RangeController, and "open"
|
||||
on a ModeController, but you can't support "open" on both RangeController
|
||||
and ModeController. Semantics stateMappings are only supported for one
|
||||
interface on the same device.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object
|
||||
"""
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
"""Support for Almond."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError, ClientSession
|
||||
import async_timeout
|
||||
from pyalmond import AbstractAlmondWebAuth, AlmondLocalAuth, WebAlmondAPI
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.auth.const import GROUP_ID_ADMIN
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
CONF_HOST,
|
||||
CONF_TYPE,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
)
|
||||
from homeassistant.core import Context, CoreState, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client,
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
event,
|
||||
intent,
|
||||
network,
|
||||
storage,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import config_flow
|
||||
from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_KEY = DOMAIN
|
||||
|
||||
ALMOND_SETUP_DELAY = 30
|
||||
|
||||
DEFAULT_OAUTH2_HOST = "https://almond.stanford.edu"
|
||||
DEFAULT_LOCAL_HOST = "http://localhost:3000"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Any(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_TYPE): TYPE_OAUTH2,
|
||||
vol.Required(CONF_CLIENT_ID): cv.string,
|
||||
vol.Required(CONF_CLIENT_SECRET): cv.string,
|
||||
vol.Optional(CONF_HOST, default=DEFAULT_OAUTH2_HOST): cv.url,
|
||||
}
|
||||
),
|
||||
vol.Schema(
|
||||
{vol.Required(CONF_TYPE): TYPE_LOCAL, vol.Required(CONF_HOST): cv.url}
|
||||
),
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Almond component."""
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
|
||||
conf = config[DOMAIN]
|
||||
|
||||
host = conf[CONF_HOST]
|
||||
|
||||
if conf[CONF_TYPE] == TYPE_OAUTH2:
|
||||
config_flow.AlmondFlowHandler.async_register_implementation(
|
||||
hass,
|
||||
config_entry_oauth2_flow.LocalOAuth2Implementation(
|
||||
hass,
|
||||
DOMAIN,
|
||||
conf[CONF_CLIENT_ID],
|
||||
conf[CONF_CLIENT_SECRET],
|
||||
f"{host}/me/api/oauth2/authorize",
|
||||
f"{host}/me/api/oauth2/token",
|
||||
),
|
||||
)
|
||||
return True
|
||||
|
||||
if not hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data={"type": TYPE_LOCAL, "host": conf[CONF_HOST]},
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Almond config entry."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
if entry.data["type"] == TYPE_LOCAL:
|
||||
auth = AlmondLocalAuth(entry.data["host"], websession)
|
||||
else:
|
||||
# OAuth2
|
||||
implementation = (
|
||||
await config_entry_oauth2_flow.async_get_config_entry_implementation(
|
||||
hass, entry
|
||||
)
|
||||
)
|
||||
oauth_session = config_entry_oauth2_flow.OAuth2Session(
|
||||
hass, entry, implementation
|
||||
)
|
||||
auth = AlmondOAuth(entry.data["host"], websession, oauth_session)
|
||||
|
||||
api = WebAlmondAPI(auth)
|
||||
agent = AlmondAgent(hass, api, entry)
|
||||
|
||||
# Hass.io does its own configuration.
|
||||
if not entry.data.get("is_hassio"):
|
||||
# If we're not starting or local, set up Almond right away
|
||||
if hass.state != CoreState.not_running or entry.data["type"] == TYPE_LOCAL:
|
||||
await _configure_almond_for_ha(hass, entry, api)
|
||||
|
||||
else:
|
||||
# OAuth2 implementations can potentially rely on the HA Cloud url.
|
||||
# This url is not be available until 30 seconds after boot.
|
||||
|
||||
async def configure_almond(_now):
|
||||
try:
|
||||
await _configure_almond_for_ha(hass, entry, api)
|
||||
except ConfigEntryNotReady:
|
||||
_LOGGER.warning(
|
||||
"Unable to configure Almond to connect to Home Assistant"
|
||||
)
|
||||
|
||||
async def almond_hass_start(_event):
|
||||
event.async_call_later(hass, ALMOND_SETUP_DELAY, configure_almond)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, almond_hass_start)
|
||||
|
||||
conversation.async_set_agent(hass, agent)
|
||||
return True
|
||||
|
||||
|
||||
async def _configure_almond_for_ha(
|
||||
hass: HomeAssistant, entry: ConfigEntry, api: WebAlmondAPI
|
||||
):
|
||||
"""Configure Almond to connect to HA."""
|
||||
try:
|
||||
if entry.data["type"] == TYPE_OAUTH2:
|
||||
# If we're connecting over OAuth2, we will only set up connection
|
||||
# with Home Assistant if we're remotely accessible.
|
||||
hass_url = network.get_url(hass, allow_internal=False, prefer_cloud=True)
|
||||
else:
|
||||
hass_url = network.get_url(hass)
|
||||
except network.NoURLAvailableError:
|
||||
# If no URL is available, we're not going to configure Almond to connect to HA.
|
||||
return
|
||||
|
||||
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
|
||||
store = storage.Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
data = await store.async_load()
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
user = None
|
||||
if "almond_user" in data:
|
||||
user = await hass.auth.async_get_user(data["almond_user"])
|
||||
|
||||
if user is None:
|
||||
user = await hass.auth.async_create_system_user(
|
||||
"Almond", group_ids=[GROUP_ID_ADMIN]
|
||||
)
|
||||
data["almond_user"] = user.id
|
||||
await store.async_save(data)
|
||||
|
||||
refresh_token = await hass.auth.async_create_refresh_token(
|
||||
user,
|
||||
# Almond will be fine as long as we restart once every 5 years
|
||||
access_token_expiration=timedelta(days=365 * 5),
|
||||
)
|
||||
|
||||
# Create long lived access token
|
||||
access_token = hass.auth.async_create_access_token(refresh_token)
|
||||
|
||||
# Store token in Almond
|
||||
try:
|
||||
async with async_timeout.timeout(30):
|
||||
await api.async_create_device(
|
||||
{
|
||||
"kind": "io.home-assistant",
|
||||
"hassUrl": hass_url,
|
||||
"accessToken": access_token,
|
||||
"refreshToken": "",
|
||||
# 5 years from now in ms.
|
||||
"accessTokenExpires": (time.time() + 60 * 60 * 24 * 365 * 5) * 1000,
|
||||
}
|
||||
)
|
||||
except (asyncio.TimeoutError, ClientError) as err:
|
||||
if isinstance(err, asyncio.TimeoutError):
|
||||
msg: str | ClientError = "Request timeout"
|
||||
else:
|
||||
msg = err
|
||||
_LOGGER.warning("Unable to configure Almond: %s", msg)
|
||||
await hass.auth.async_remove_refresh_token(refresh_token)
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
# Clear all other refresh tokens
|
||||
for token in list(user.refresh_tokens.values()):
|
||||
if token.id != refresh_token.id:
|
||||
await hass.auth.async_remove_refresh_token(token)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload Almond."""
|
||||
conversation.async_set_agent(hass, None)
|
||||
return True
|
||||
|
||||
|
||||
class AlmondOAuth(AbstractAlmondWebAuth):
|
||||
"""Almond Authentication using OAuth2."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str,
|
||||
websession: ClientSession,
|
||||
oauth_session: config_entry_oauth2_flow.OAuth2Session,
|
||||
) -> None:
|
||||
"""Initialize Almond auth."""
|
||||
super().__init__(host, websession)
|
||||
self._oauth_session = oauth_session
|
||||
|
||||
async def async_get_access_token(self):
|
||||
"""Return a valid access token."""
|
||||
if not self._oauth_session.valid_token:
|
||||
await self._oauth_session.async_ensure_token_valid()
|
||||
|
||||
return self._oauth_session.token["access_token"]
|
||||
|
||||
|
||||
class AlmondAgent(conversation.AbstractConversationAgent):
|
||||
"""Almond conversation agent."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: WebAlmondAPI, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the agent."""
|
||||
self.hass = hass
|
||||
self.api = api
|
||||
self.entry = entry
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return {"name": "Powered by Almond", "url": "https://almond.stanford.edu/"}
|
||||
|
||||
async def async_get_onboarding(self):
|
||||
"""Get onboard url if not onboarded."""
|
||||
if self.entry.data.get("onboarded"):
|
||||
return None
|
||||
|
||||
host = self.entry.data["host"]
|
||||
if self.entry.data.get("is_hassio"):
|
||||
host = "/core_almond"
|
||||
return {
|
||||
"text": (
|
||||
"Would you like to opt-in to share your anonymized commands with"
|
||||
" Stanford to improve Almond's responses?"
|
||||
),
|
||||
"url": f"{host}/conversation",
|
||||
}
|
||||
|
||||
async def async_set_onboarding(self, shown):
|
||||
"""Set onboarding status."""
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.entry, data={**self.entry.data, "onboarded": shown}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def async_process(
|
||||
self,
|
||||
text: str,
|
||||
context: Context,
|
||||
conversation_id: str | None = None,
|
||||
language: str | None = None,
|
||||
) -> conversation.ConversationResult | None:
|
||||
"""Process a sentence."""
|
||||
response = await self.api.async_converse_text(text, conversation_id)
|
||||
language = language or self.hass.config.language
|
||||
|
||||
first_choice = True
|
||||
buffer = ""
|
||||
for message in response["messages"]:
|
||||
if message["type"] == "text":
|
||||
buffer += f"\n{message['text']}"
|
||||
elif message["type"] == "picture":
|
||||
buffer += f"\n Picture: {message['url']}"
|
||||
elif message["type"] == "rdl":
|
||||
buffer += (
|
||||
f"\n Link: {message['rdl']['displayTitle']} "
|
||||
f"{message['rdl']['webCallback']}"
|
||||
)
|
||||
elif message["type"] == "choice":
|
||||
if first_choice:
|
||||
first_choice = False
|
||||
else:
|
||||
buffer += ","
|
||||
buffer += f" {message['title']}"
|
||||
|
||||
intent_response = intent.IntentResponse(language=language)
|
||||
intent_response.async_set_speech(buffer.strip())
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=conversation_id
|
||||
)
|
||||
@@ -1,124 +0,0 @@
|
||||
"""Config flow to connect with Home Assistant."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
import async_timeout
|
||||
from pyalmond import AlmondLocalAuth, WebAlmondAPI
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant import core, data_entry_flow
|
||||
from homeassistant.components.hassio import HassioServiceInfo
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
|
||||
|
||||
from .const import DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
|
||||
|
||||
|
||||
async def async_verify_local_connection(hass: core.HomeAssistant, host: str):
|
||||
"""Verify that a local connection works."""
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
api = WebAlmondAPI(AlmondLocalAuth(host, websession))
|
||||
|
||||
try:
|
||||
async with async_timeout.timeout(10):
|
||||
await api.async_list_apps()
|
||||
|
||||
return True
|
||||
except (asyncio.TimeoutError, ClientError):
|
||||
return False
|
||||
|
||||
|
||||
class AlmondFlowHandler(
|
||||
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
|
||||
):
|
||||
"""Implementation of the Almond OAuth2 config flow."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
|
||||
host = None
|
||||
hassio_discovery = None
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
@property
|
||||
def extra_authorize_data(self) -> dict:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
return {"scope": "profile user-read user-read-results user-exec-command"}
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow start."""
|
||||
# Only allow 1 instance.
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
return await super().async_step_user(user_input)
|
||||
|
||||
async def async_step_auth(self, user_input=None):
|
||||
"""Handle authorize step."""
|
||||
result = await super().async_step_auth(user_input)
|
||||
|
||||
if result["type"] == data_entry_flow.FlowResultType.EXTERNAL_STEP:
|
||||
self.host = str(URL(result["url"]).with_path("me"))
|
||||
|
||||
return result
|
||||
|
||||
async def async_oauth_create_entry(self, data: dict) -> FlowResult:
|
||||
"""Create an entry for the flow.
|
||||
|
||||
Ok to override if you want to fetch extra info or even add another step.
|
||||
"""
|
||||
data["type"] = TYPE_OAUTH2
|
||||
data["host"] = self.host
|
||||
return self.async_create_entry(title=self.flow_impl.name, data=data)
|
||||
|
||||
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
|
||||
"""Import data."""
|
||||
# Only allow 1 instance.
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
if not await async_verify_local_connection(self.hass, user_input["host"]):
|
||||
self.logger.warning(
|
||||
"Aborting import of Almond because we're unable to connect"
|
||||
)
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
|
||||
return self.async_create_entry(
|
||||
title="Configuration.yaml",
|
||||
data={"type": TYPE_LOCAL, "host": user_input["host"]},
|
||||
)
|
||||
|
||||
async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
|
||||
"""Receive a Hass.io discovery."""
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
self.hassio_discovery = discovery_info.config
|
||||
|
||||
return await self.async_step_hassio_confirm()
|
||||
|
||||
async def async_step_hassio_confirm(self, user_input=None):
|
||||
"""Confirm a Hass.io discovery."""
|
||||
data = self.hassio_discovery
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=data["addon"],
|
||||
data={
|
||||
"is_hassio": True,
|
||||
"type": TYPE_LOCAL,
|
||||
"host": f"http://{data['host']}:{data['port']}",
|
||||
},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="hassio_confirm",
|
||||
description_placeholders={"addon": data["addon"]},
|
||||
)
|
||||
@@ -1,4 +0,0 @@
|
||||
"""Constants for the Almond integration."""
|
||||
DOMAIN = "almond"
|
||||
TYPE_OAUTH2 = "oauth2"
|
||||
TYPE_LOCAL = "local"
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"domain": "almond",
|
||||
"name": "Almond",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/almond",
|
||||
"dependencies": ["auth", "conversation"],
|
||||
"codeowners": ["@gcampax", "@balloob"],
|
||||
"requirements": ["pyalmond==0.0.2"],
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyalmond"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||
},
|
||||
"hassio_confirm": {
|
||||
"title": "Almond via Home Assistant add-on",
|
||||
"description": "Do you want to configure Home Assistant to connect to Almond provided by the add-on: {addon}?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"missing_configuration": "\u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430 \u043a\u0430\u043a \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Almond.",
|
||||
"single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
|
||||
},
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "\u0418\u0437\u0431\u043e\u0440 \u043d\u0430 \u043c\u0435\u0442\u043e\u0434 \u0437\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0430\u0446\u0438\u044f"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "Ha fallat la connexi\u00f3",
|
||||
"missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3.",
|
||||
"no_url_available": "No hi ha cap URL disponible. Per a m\u00e9s informaci\u00f3 sobre aquest error, [consulta la secci\u00f3 d'ajuda]({docs_url})",
|
||||
"single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Vols configurar Home Assistant perqu\u00e8 es connecti amb Almond proporcionat pel complement: {addon}?",
|
||||
"title": "Almond via complement de Home Assistant"
|
||||
},
|
||||
"pick_implementation": {
|
||||
"title": "Selecciona el m\u00e8tode d'autenticaci\u00f3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
|
||||
"missing_configuration": "Komponenta nen\u00ed nastavena. Postupujte podle dokumentace.",
|
||||
"no_url_available": "Nen\u00ed k dispozici \u017e\u00e1dn\u00e1 adresa URL. Informace o t\u00e9to chyb\u011b naleznete [v sekci n\u00e1pov\u011bdy]({docs_url})",
|
||||
"single_instance_allowed": "Ji\u017e nastaveno. Je mo\u017en\u00e1 pouze jedin\u00e1 konfigurace."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Chcete nakonfigurovat slu\u017ebu Home Assistant pro p\u0159ipojen\u00ed k Almond pomoc\u00ed Supervisor {addon}?",
|
||||
"title": "Almond prost\u0159ednictv\u00edm dopl\u0148ku Supervisor"
|
||||
},
|
||||
"pick_implementation": {
|
||||
"title": "Vyberte metodu ov\u011b\u0159en\u00ed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "Kan ikke oprette forbindelse til Almond-serveren.",
|
||||
"missing_configuration": "Tjek venligst dokumentationen om, hvordan man indstiller Almond."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "Vil du konfigurere Home Assistant til at oprette forbindelse til Almond leveret af Supervisor-tilf\u00f8jelsen: {addon}?",
|
||||
"title": "Almond via Supervisor-tilf\u00f8jelse"
|
||||
},
|
||||
"pick_implementation": {
|
||||
"title": "V\u00e6lg godkendelsesmetode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"cannot_connect": "Verbindung fehlgeschlagen",
|
||||
"missing_configuration": "Die Komponente ist nicht konfiguriert. Bitte der Dokumentation folgen.",
|
||||
"no_url_available": "Keine URL verf\u00fcgbar. Informationen zu diesem Fehler findest du [im Hilfebereich]({docs_url}).",
|
||||
"single_instance_allowed": "Bereits konfiguriert. Nur eine einzige Konfiguration m\u00f6glich."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"description": "M\u00f6chtest du Home Assistant so konfigurieren, dass eine Verbindung mit Almond als Supervisor-Add-On hergestellt wird: {addon}?",
|
||||
"title": "Almond \u00fcber das Supervisor Add-on"
|
||||
},
|
||||
"pick_implementation": {
|
||||
"title": "W\u00e4hle die Authentifizierungsmethode"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user