forked from home-assistant/core
Compare commits
1062 Commits
2022.7.0b2
...
2022.8.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
795ed9fbf6 | ||
|
|
ec4ff824ad | ||
|
|
f4a09455c0 | ||
|
|
bf88448ffd | ||
|
|
bae01f188a | ||
|
|
d142618297 | ||
|
|
d91e9f7f98 | ||
|
|
70541eac61 | ||
|
|
30a5e396d3 | ||
|
|
ae99b53757 | ||
|
|
71a6128c65 | ||
|
|
69ea07f29d | ||
|
|
41faf9092a | ||
|
|
ef6285800f | ||
|
|
bd40d6f332 | ||
|
|
d10e2336e2 | ||
|
|
875de80b41 | ||
|
|
666e938b14 | ||
|
|
972aad0e99 | ||
|
|
872b2f56ac | ||
|
|
e2fae855e7 | ||
|
|
fdde4d540d | ||
|
|
5606b4026f | ||
|
|
84b8029c6a | ||
|
|
294cc3ac6e | ||
|
|
99c20223e5 | ||
|
|
451ab47caa | ||
|
|
738423056e | ||
|
|
a3ea881a01 | ||
|
|
2dcc886b2f | ||
|
|
6e688b2b7f | ||
|
|
0fd38ef9f8 | ||
|
|
56c80cd31a | ||
|
|
303d8b05d1 | ||
|
|
972c05eac8 | ||
|
|
e5088d7e84 | ||
|
|
6c1597ff98 | ||
|
|
edac82487d | ||
|
|
5213148fa8 | ||
|
|
8e3f5ec470 | ||
|
|
af90159e7c | ||
|
|
f01b0a1a62 | ||
|
|
ee2acabcbe | ||
|
|
79b371229d | ||
|
|
0f6b059e3e | ||
|
|
bfb2867e8d | ||
|
|
c9581f6a2e | ||
|
|
e96903fddf | ||
|
|
5026bff426 | ||
|
|
4b63aa7f15 | ||
|
|
1c2dd78e4c | ||
|
|
9cf11cf6ed | ||
|
|
8971a2073e | ||
|
|
bfa64d2e01 | ||
|
|
9c21d56539 | ||
|
|
8bfc352524 | ||
|
|
0e7bf35e4a | ||
|
|
1dd701a89a | ||
|
|
d266b1ced6 | ||
|
|
6727dab330 | ||
|
|
42509056bd | ||
|
|
a370e4f4b0 | ||
|
|
a17e99f714 | ||
|
|
db227a888d | ||
|
|
1808dd3d84 | ||
|
|
31fed328ce | ||
|
|
1a030f118a | ||
|
|
a4049e93d8 | ||
|
|
854ca853dc | ||
|
|
2710e4b5ec | ||
|
|
450af52bac | ||
|
|
60da54558e | ||
|
|
11319defae | ||
|
|
6340da72a5 | ||
|
|
5c9d557b10 | ||
|
|
d2955a48b0 | ||
|
|
d2b98fa285 | ||
|
|
8ef3ca2daf | ||
|
|
80a053a4cd | ||
|
|
81ee24738b | ||
|
|
29f6d7818a | ||
|
|
bc1e371cae | ||
|
|
42a1f6ca20 | ||
|
|
d85129c527 | ||
|
|
ad14b5f3d7 | ||
|
|
51a6899a60 | ||
|
|
d2dc83c4c7 | ||
|
|
d7a418a219 | ||
|
|
a78da6a000 | ||
|
|
690f051a87 | ||
|
|
c22cb13bd0 | ||
|
|
213812f087 | ||
|
|
19b0961084 | ||
|
|
e073f6b439 | ||
|
|
c4906414ea | ||
|
|
cc9a130f58 | ||
|
|
c90a223cb6 | ||
|
|
2eddbf2381 | ||
|
|
654e26052b | ||
|
|
676664022d | ||
|
|
ed57951571 | ||
|
|
b9ee81dfc3 | ||
|
|
da00f5ba1e | ||
|
|
30cd087f6f | ||
|
|
66afd1e696 | ||
|
|
23488f392b | ||
|
|
7140a9d025 | ||
|
|
4f671bccbc | ||
|
|
6b588d41ff | ||
|
|
b962a6e767 | ||
|
|
a332eb154c | ||
|
|
75747ce319 | ||
|
|
c6038380d6 | ||
|
|
990975e908 | ||
|
|
2a58bf06c1 | ||
|
|
5ab549653b | ||
|
|
ffd2813150 | ||
|
|
ebf91fe46b | ||
|
|
e330147751 | ||
|
|
26a3621bb3 | ||
|
|
58265664d1 | ||
|
|
d205fb5064 | ||
|
|
38ae2f4e9e | ||
|
|
d84bc20a58 | ||
|
|
a3276e00b9 | ||
|
|
bdb627539e | ||
|
|
240890e496 | ||
|
|
e2a9ab1831 | ||
|
|
8f8bccd982 | ||
|
|
26c475d3dc | ||
|
|
0f0b51bee7 | ||
|
|
241ffe07b9 | ||
|
|
c3c5442467 | ||
|
|
d7827d9902 | ||
|
|
176d44190e | ||
|
|
48b97a1f2d | ||
|
|
f4defb660b | ||
|
|
dfd503cc1a | ||
|
|
97c6c949e7 | ||
|
|
c469bdea75 | ||
|
|
2b1fbbfae3 | ||
|
|
e4e36b51b6 | ||
|
|
53870dd0bc | ||
|
|
38909855bf | ||
|
|
f98d95c76f | ||
|
|
2bf10799ed | ||
|
|
4be623a492 | ||
|
|
a000687eb5 | ||
|
|
c10ed6edba | ||
|
|
2dc318be54 | ||
|
|
b4d2c25f8e | ||
|
|
e7ff97bac0 | ||
|
|
add9ff5736 | ||
|
|
937fd490f2 | ||
|
|
96587c1227 | ||
|
|
15e6fcca41 | ||
|
|
7811518d7c | ||
|
|
70731c0bc7 | ||
|
|
3b8650d053 | ||
|
|
15f87ca0a1 | ||
|
|
8ffdbfc462 | ||
|
|
9dc0544835 | ||
|
|
49854b809c | ||
|
|
314778cb50 | ||
|
|
6254142b8a | ||
|
|
699fff08ed | ||
|
|
07a433a516 | ||
|
|
f8b115dd9d | ||
|
|
aaf5837759 | ||
|
|
a4d4170279 | ||
|
|
51c3836ec2 | ||
|
|
bbdce93291 | ||
|
|
5687150786 | ||
|
|
5e6217f20c | ||
|
|
89493f2d7f | ||
|
|
33c635809c | ||
|
|
129b42cd23 | ||
|
|
6868865e3e | ||
|
|
fb52f5098f | ||
|
|
c5912f0fae | ||
|
|
0ff34f232c | ||
|
|
e2dd2c9424 | ||
|
|
2b1a5e5549 | ||
|
|
aaaca0b2bd | ||
|
|
184e254a43 | ||
|
|
bb4ee1ba32 | ||
|
|
1e85ddabfd | ||
|
|
157f7292d7 | ||
|
|
cb17a01e48 | ||
|
|
fad7a6cb08 | ||
|
|
c63a838b47 | ||
|
|
3ee0ca8550 | ||
|
|
d39afaa264 | ||
|
|
35f4220f4e | ||
|
|
e6802f4f7e | ||
|
|
7d895c79e8 | ||
|
|
516324ff54 | ||
|
|
af7df260a0 | ||
|
|
9ad273a59f | ||
|
|
5cb4bbd906 | ||
|
|
0006629ca2 | ||
|
|
fc43ee772c | ||
|
|
d756936a4e | ||
|
|
674a59f138 | ||
|
|
9173aef1ef | ||
|
|
0407fc4581 | ||
|
|
ec4835ef04 | ||
|
|
a3950937e0 | ||
|
|
58b7f9a032 | ||
|
|
eaee923e4c | ||
|
|
a98f658854 | ||
|
|
47713d9686 | ||
|
|
5e45b0baf9 | ||
|
|
b57e0d13b4 | ||
|
|
ce4e53938c | ||
|
|
1f73a553c8 | ||
|
|
2b617e3885 | ||
|
|
9c725bc106 | ||
|
|
7fd47717cf | ||
|
|
4ea532b3ea | ||
|
|
c7ddc595ed | ||
|
|
e87c2b9e25 | ||
|
|
ea354f3d5f | ||
|
|
a287abe763 | ||
|
|
274584f2a4 | ||
|
|
3aa75f3fcc | ||
|
|
2df20e7a42 | ||
|
|
f4e7436421 | ||
|
|
e3fb4ceb09 | ||
|
|
a813cf987b | ||
|
|
3df7892454 | ||
|
|
b8ae883f18 | ||
|
|
de46243ce5 | ||
|
|
fc9a0ba46b | ||
|
|
f31f2cca07 | ||
|
|
4a50010458 | ||
|
|
22ca28b93d | ||
|
|
511af3c455 | ||
|
|
bbb9443b00 | ||
|
|
d890598da7 | ||
|
|
c9ae409d9a | ||
|
|
2d4bd4d7c1 | ||
|
|
c0e6852077 | ||
|
|
e18819c678 | ||
|
|
f94a79b409 | ||
|
|
9fae638f65 | ||
|
|
0df08b6b0c | ||
|
|
198167a2c8 | ||
|
|
79be87f9ce | ||
|
|
ba71a3c24d | ||
|
|
7075032bf7 | ||
|
|
82c92b5634 | ||
|
|
a499dfb8ff | ||
|
|
da131beced | ||
|
|
19f82e5201 | ||
|
|
7cf2d1759d | ||
|
|
c5afaa2e6a | ||
|
|
8300d5b89e | ||
|
|
240bbfa080 | ||
|
|
2951a941b6 | ||
|
|
759add5184 | ||
|
|
8d6247446b | ||
|
|
b71e3397fd | ||
|
|
5c234a3504 | ||
|
|
5e10716dd8 | ||
|
|
b60a59270c | ||
|
|
edaebcd85d | ||
|
|
8e86124470 | ||
|
|
326e05dcf1 | ||
|
|
cb543a21b3 | ||
|
|
402e533fef | ||
|
|
6bb51782fa | ||
|
|
88b9a51811 | ||
|
|
5b555066ea | ||
|
|
19db6ecf6d | ||
|
|
38bccadaa6 | ||
|
|
20b6c4c48e | ||
|
|
c05905ebda | ||
|
|
148f963510 | ||
|
|
e9697872c8 | ||
|
|
fd6ffef52f | ||
|
|
9d0a252ca7 | ||
|
|
606d544157 | ||
|
|
06c8eb0304 | ||
|
|
630c28d253 | ||
|
|
f0eea62c1e | ||
|
|
dddd4e24e2 | ||
|
|
a612d7a0f3 | ||
|
|
06115bcbff | ||
|
|
b0261dd2eb | ||
|
|
67e16d77e8 | ||
|
|
36138afb93 | ||
|
|
90ca3fe350 | ||
|
|
975378ba44 | ||
|
|
04c6b9c519 | ||
|
|
6cb1794720 | ||
|
|
05b463b282 | ||
|
|
f3c4bf571b | ||
|
|
8523c66bb5 | ||
|
|
b1ed1543c8 | ||
|
|
1d7d2875e1 | ||
|
|
41f6383957 | ||
|
|
baeb55e313 | ||
|
|
c861259749 | ||
|
|
46551a50ef | ||
|
|
87797c8b66 | ||
|
|
ca1f0909fb | ||
|
|
ac858cc2b5 | ||
|
|
6da25c733e | ||
|
|
cd0656bab0 | ||
|
|
01c105b89c | ||
|
|
7402dc824e | ||
|
|
67fc1ac40a | ||
|
|
e692d2e284 | ||
|
|
4ac7d68552 | ||
|
|
e53a072e8a | ||
|
|
787f55e513 | ||
|
|
0a11a623a5 | ||
|
|
55ef33af26 | ||
|
|
5c2ef50fca | ||
|
|
630f731020 | ||
|
|
219d1a8a1e | ||
|
|
75641b6cd4 | ||
|
|
340da786af | ||
|
|
7f43064f36 | ||
|
|
97b6912856 | ||
|
|
8b270cb487 | ||
|
|
8232a780eb | ||
|
|
34c30f5ab9 | ||
|
|
75aea68b75 | ||
|
|
3a2beb2212 | ||
|
|
bccdb29edc | ||
|
|
8b4cf288e3 | ||
|
|
1b61d72eaf | ||
|
|
fdaaed6523 | ||
|
|
b749622c01 | ||
|
|
079460d2dd | ||
|
|
a91ca46342 | ||
|
|
7ba3227d52 | ||
|
|
48e82ff62f | ||
|
|
4395b967f2 | ||
|
|
518001f00b | ||
|
|
87cfe21567 | ||
|
|
877a4030aa | ||
|
|
8ad2bed363 | ||
|
|
fb4aff25a2 | ||
|
|
3920844dca | ||
|
|
cd3e99564f | ||
|
|
95e07508e5 | ||
|
|
712492b066 | ||
|
|
39dc9aa179 | ||
|
|
a3b2b5c328 | ||
|
|
5ef92e5e95 | ||
|
|
11e7ddaa71 | ||
|
|
460837e453 | ||
|
|
fe97f6791d | ||
|
|
2db8b154c9 | ||
|
|
93425b0e4d | ||
|
|
05d2b955ee | ||
|
|
b62ebbe974 | ||
|
|
0e59e8b925 | ||
|
|
3d31e62683 | ||
|
|
2b752355d6 | ||
|
|
8c7e329754 | ||
|
|
d989e4373d | ||
|
|
51ed9ee59d | ||
|
|
8a48d54951 | ||
|
|
0f81d1d14a | ||
|
|
1626c53c13 | ||
|
|
1a1eeb2274 | ||
|
|
3193ea3359 | ||
|
|
07b4d48e7c | ||
|
|
d09fff595c | ||
|
|
b04c3e9adc | ||
|
|
672883e19d | ||
|
|
c29bd48373 | ||
|
|
503e88642e | ||
|
|
e4f6f738e8 | ||
|
|
e02a24529f | ||
|
|
32311f240b | ||
|
|
5ae5ae5392 | ||
|
|
4b036cbad9 | ||
|
|
6b60fb9541 | ||
|
|
b6d235c0c2 | ||
|
|
25b874a609 | ||
|
|
403bbda959 | ||
|
|
c3d536b255 | ||
|
|
8e8c6e2394 | ||
|
|
24b3b5fc46 | ||
|
|
e65018fb85 | ||
|
|
d05160a402 | ||
|
|
a9e9d7b112 | ||
|
|
ebabaeb364 | ||
|
|
b37f15b1d5 | ||
|
|
41e4b38c3a | ||
|
|
1354952977 | ||
|
|
983bcfa935 | ||
|
|
e75d7dfb75 | ||
|
|
9f33a0d6dd | ||
|
|
45d1f8bc55 | ||
|
|
5928a7d494 | ||
|
|
8b912d1d91 | ||
|
|
7adb0f0ef5 | ||
|
|
3144d179e0 | ||
|
|
b3ef6f4d04 | ||
|
|
6f5e4ca503 | ||
|
|
9d0c91d648 | ||
|
|
bfe34adf6e | ||
|
|
11f80762ea | ||
|
|
6fdb414b58 | ||
|
|
ca5065a627 | ||
|
|
943e0b9cf7 | ||
|
|
e522b6e3b8 | ||
|
|
4d5673013b | ||
|
|
a8bb00f305 | ||
|
|
91f2550bc3 | ||
|
|
a95c2c7850 | ||
|
|
939c33b1dc | ||
|
|
5beddb13c0 | ||
|
|
98dae902a1 | ||
|
|
27e3ff9c69 | ||
|
|
460f522d6d | ||
|
|
8d63f81821 | ||
|
|
503b31fb15 | ||
|
|
cd223d91bb | ||
|
|
d8f3044ffa | ||
|
|
9a27f1437d | ||
|
|
2eebda63fd | ||
|
|
ba8a530d19 | ||
|
|
79a0940932 | ||
|
|
59c99e0d60 | ||
|
|
2f92c47fe3 | ||
|
|
4ceda65889 | ||
|
|
08d6487997 | ||
|
|
cb12f77e33 | ||
|
|
ae4b1967a3 | ||
|
|
514e826fed | ||
|
|
b9c8d65940 | ||
|
|
ecc219fbc1 | ||
|
|
859189421b | ||
|
|
20d70337d5 | ||
|
|
686449cef6 | ||
|
|
c52d4c64bf | ||
|
|
952c90efcd | ||
|
|
393610c534 | ||
|
|
8d88562d40 | ||
|
|
0f3cc4a4aa | ||
|
|
13cea26e74 | ||
|
|
027cdbdb38 | ||
|
|
d0c4d39ae2 | ||
|
|
1ce4714722 | ||
|
|
dbcd98d029 | ||
|
|
2106c9f247 | ||
|
|
97fd669924 | ||
|
|
c6c063e8c5 | ||
|
|
48f4b51a1d | ||
|
|
06e905054e | ||
|
|
3f3ed3a2c5 | ||
|
|
d2e5d01aca | ||
|
|
cba3c8cf65 | ||
|
|
911402e747 | ||
|
|
3e98ac180c | ||
|
|
4e29bdf715 | ||
|
|
a23b427025 | ||
|
|
b4003713b6 | ||
|
|
c185e636ed | ||
|
|
99d39b203f | ||
|
|
e4f1fa48d3 | ||
|
|
c56caca182 | ||
|
|
62d77a135b | ||
|
|
b231eea0c6 | ||
|
|
5eaa15138c | ||
|
|
d401faac7c | ||
|
|
674e02914b | ||
|
|
326ffdcd49 | ||
|
|
b5e24048db | ||
|
|
b1c07ac17a | ||
|
|
a5693c083f | ||
|
|
2dde3d02cc | ||
|
|
98807f7efc | ||
|
|
700081e160 | ||
|
|
08a361dab9 | ||
|
|
ea6bb370a6 | ||
|
|
4a3d047dff | ||
|
|
ff297cb902 | ||
|
|
03e3ebb238 | ||
|
|
61cc9f5288 | ||
|
|
fef1b842ce | ||
|
|
d004adf833 | ||
|
|
72906bf154 | ||
|
|
09f37fc522 | ||
|
|
9a4a7e2f4d | ||
|
|
f0cc565f6c | ||
|
|
e7ae2fada7 | ||
|
|
75892385bb | ||
|
|
6184f0557d | ||
|
|
bdc63b692b | ||
|
|
5287980f48 | ||
|
|
b7cdf5412b | ||
|
|
3d2101cac5 | ||
|
|
54a939e223 | ||
|
|
3f6e930489 | ||
|
|
874043f596 | ||
|
|
124bfe1629 | ||
|
|
5f08052f40 | ||
|
|
fde3489e86 | ||
|
|
5e46fa6f8b | ||
|
|
56da7d0ad0 | ||
|
|
bb14f83b94 | ||
|
|
666f715e76 | ||
|
|
1725948d4a | ||
|
|
89985b93fb | ||
|
|
2286dea636 | ||
|
|
a31dde9cb4 | ||
|
|
a3c1926da5 | ||
|
|
20432ccc76 | ||
|
|
e16bd1e471 | ||
|
|
c260413e2a | ||
|
|
28c082a080 | ||
|
|
debd475a6d | ||
|
|
b60f6c7cdd | ||
|
|
51c17197c5 | ||
|
|
3bccac9949 | ||
|
|
169264db66 | ||
|
|
c9df5888c2 | ||
|
|
08ff1b8986 | ||
|
|
a720b2989a | ||
|
|
2aa98da624 | ||
|
|
1d2e64e3dc | ||
|
|
60e170c863 | ||
|
|
533ae85a49 | ||
|
|
1555f706e5 | ||
|
|
f052c3ca74 | ||
|
|
89e87119f2 | ||
|
|
9e99ea68fb | ||
|
|
b105b0fbcb | ||
|
|
98c3bc56b5 | ||
|
|
06e7f71891 | ||
|
|
55ae0228a9 | ||
|
|
5c882429d4 | ||
|
|
51a4c98562 | ||
|
|
620d2ed8fd | ||
|
|
bdc4171e37 | ||
|
|
f738d39ad9 | ||
|
|
a34e72f3a1 | ||
|
|
c45313e9de | ||
|
|
4d81d056da | ||
|
|
3f53022b50 | ||
|
|
d31a0c8dca | ||
|
|
cc79c3d6e1 | ||
|
|
5e7174a5f4 | ||
|
|
8259ce9868 | ||
|
|
3166d4895f | ||
|
|
1768315c50 | ||
|
|
d1ffc7e9e3 | ||
|
|
755abbe2d0 | ||
|
|
ffeac9714f | ||
|
|
4a36318d56 | ||
|
|
2169d839ce | ||
|
|
34f1d5e094 | ||
|
|
b7a6f4e220 | ||
|
|
1e5ada0f9d | ||
|
|
ad82352dae | ||
|
|
3ca04aa33b | ||
|
|
a3fd5acf3f | ||
|
|
41ec8cd354 | ||
|
|
b54fe14a10 | ||
|
|
d40978742c | ||
|
|
96ecbe4388 | ||
|
|
397f94ee50 | ||
|
|
b0fde206b8 | ||
|
|
cf612c4bec | ||
|
|
5489b2111a | ||
|
|
5fdae0fc5b | ||
|
|
7283d1b7fb | ||
|
|
cd25bc1901 | ||
|
|
ff1cdb4de7 | ||
|
|
5930f056a8 | ||
|
|
6a37600936 | ||
|
|
dfe840c045 | ||
|
|
f4953e6e9b | ||
|
|
5d8e1b8387 | ||
|
|
7b9a0eed22 | ||
|
|
2e228b2608 | ||
|
|
da027fa390 | ||
|
|
ef025bccc0 | ||
|
|
2d2fd3e48f | ||
|
|
5774f2e7b9 | ||
|
|
2986a2f01b | ||
|
|
14baaf4b67 | ||
|
|
28a34a1f89 | ||
|
|
7c2bd319f1 | ||
|
|
ba18e11308 | ||
|
|
8d6925b3ab | ||
|
|
75abf87611 | ||
|
|
4a39087fe7 | ||
|
|
a30d7e5104 | ||
|
|
964bb63da6 | ||
|
|
7e0515b119 | ||
|
|
63706d2f67 | ||
|
|
924dce1b86 | ||
|
|
01ca7f657c | ||
|
|
6fd47d035e | ||
|
|
6ac05784a6 | ||
|
|
7b5cf63a46 | ||
|
|
e1e85caf18 | ||
|
|
fa51a39f1d | ||
|
|
c2fefe03b2 | ||
|
|
eb922b2a1f | ||
|
|
af2feb3d40 | ||
|
|
73a8ae35c2 | ||
|
|
fe1c23321e | ||
|
|
06a4c226fd | ||
|
|
ee4749b207 | ||
|
|
c1a4dc2f22 | ||
|
|
8820ce0bdd | ||
|
|
5f4713a200 | ||
|
|
9d2c213903 | ||
|
|
66e27945ac | ||
|
|
ce353460b3 | ||
|
|
ab9950621b | ||
|
|
d244d06711 | ||
|
|
ba3f287c01 | ||
|
|
ca93aacc57 | ||
|
|
c80066072c | ||
|
|
0d6bf08ff6 | ||
|
|
7d27dad190 | ||
|
|
f6fd7d115e | ||
|
|
ed1545fab0 | ||
|
|
6e1cd4c48a | ||
|
|
81f74d2053 | ||
|
|
34b8f2b283 | ||
|
|
eb92f0e16c | ||
|
|
a3d0719c49 | ||
|
|
f4e61eff18 | ||
|
|
5451ccd2b5 | ||
|
|
53502eb662 | ||
|
|
d0f71d2e53 | ||
|
|
7a729aed54 | ||
|
|
abb11009b4 | ||
|
|
d697bb53c5 | ||
|
|
261c52e260 | ||
|
|
9ff77e0fa1 | ||
|
|
a4517a4c1d | ||
|
|
792c825699 | ||
|
|
08887a6faa | ||
|
|
f15d3fc5db | ||
|
|
176e2754ec | ||
|
|
4418e6c4b6 | ||
|
|
8054e309b3 | ||
|
|
c9330841a1 | ||
|
|
c2d8335cc5 | ||
|
|
b4e5c95e03 | ||
|
|
a19ab389fc | ||
|
|
02452c7632 | ||
|
|
20f77ef832 | ||
|
|
adbcd8adb4 | ||
|
|
bd069966f2 | ||
|
|
986a86ebed | ||
|
|
01eae3687a | ||
|
|
2f570fa715 | ||
|
|
2ba285b8e5 | ||
|
|
07f4efcd83 | ||
|
|
357fe2a722 | ||
|
|
2accc4c07d | ||
|
|
79b4f8ce0c | ||
|
|
e233024533 | ||
|
|
43527d8d19 | ||
|
|
59471a6fbd | ||
|
|
f5d18108d0 | ||
|
|
5f728b955e | ||
|
|
cdab725bf4 | ||
|
|
6f28e4bfee | ||
|
|
8285f42d26 | ||
|
|
267057c989 | ||
|
|
d6ceebbb68 | ||
|
|
c81d63e070 | ||
|
|
4a38be2924 | ||
|
|
07444dba2a | ||
|
|
4433065438 | ||
|
|
b070bb8ef0 | ||
|
|
c5253d3da0 | ||
|
|
bb4b2014fc | ||
|
|
23f2e9014e | ||
|
|
1aeb15050e | ||
|
|
98a27ed3ed | ||
|
|
edf304718c | ||
|
|
5971ab6549 | ||
|
|
f95c9d0f02 | ||
|
|
ae4f2a0e34 | ||
|
|
a4d5ecb8ec | ||
|
|
c92936cc7b | ||
|
|
c241e876ef | ||
|
|
59170d3c54 | ||
|
|
c9aa3c112a | ||
|
|
edaafadde0 | ||
|
|
240a83239a | ||
|
|
6f9fcdff99 | ||
|
|
52130b227e | ||
|
|
70ceccb06a | ||
|
|
3429a75cc5 | ||
|
|
cb4d2d1b26 | ||
|
|
f7aea76e73 | ||
|
|
80727ff952 | ||
|
|
d40ad96916 | ||
|
|
2f37500443 | ||
|
|
40ee7bab8f | ||
|
|
d80d16aaa4 | ||
|
|
157d6dc83f | ||
|
|
0f813b61c3 | ||
|
|
81e7eb623b | ||
|
|
63582c3f98 | ||
|
|
db111645c2 | ||
|
|
e3242d8d16 | ||
|
|
d33779d3a0 | ||
|
|
e529ef7b0e | ||
|
|
bb974ebf7e | ||
|
|
36bb34f391 | ||
|
|
d04e77ef7f | ||
|
|
d49c58cf87 | ||
|
|
3922141f5c | ||
|
|
da133a7f05 | ||
|
|
f53bf1127f | ||
|
|
81cdbf4f9b | ||
|
|
16900dcef1 | ||
|
|
d37ad20894 | ||
|
|
cdcfd1149e | ||
|
|
5b32eea3d0 | ||
|
|
2cc9db5468 | ||
|
|
7cac1ae6a3 | ||
|
|
0b1f29b2b9 | ||
|
|
ac21dc929f | ||
|
|
ea1fc6b5d3 | ||
|
|
5360e56d09 | ||
|
|
e74f7d13ab | ||
|
|
0f33c08dca | ||
|
|
5defe67269 | ||
|
|
8ce897f462 | ||
|
|
4080d2b0da | ||
|
|
cdcc73a414 | ||
|
|
1c4fee65c0 | ||
|
|
24ca656372 | ||
|
|
04bb2d1e5d | ||
|
|
d208bd461d | ||
|
|
1cf8b76124 | ||
|
|
3aff5fd2e6 | ||
|
|
72d134be52 | ||
|
|
124c8e8f73 | ||
|
|
85148b343d | ||
|
|
cd03c49fc2 | ||
|
|
3a5cca3ff2 | ||
|
|
8bf692d046 | ||
|
|
a9c97e5d3a | ||
|
|
430d3e4604 | ||
|
|
79b34090e8 | ||
|
|
e17db1fd0c | ||
|
|
825e696d26 | ||
|
|
f4c333626e | ||
|
|
36357fe45d | ||
|
|
cdaefc8fda | ||
|
|
a697672944 | ||
|
|
5080246fb6 | ||
|
|
c27fbce7d0 | ||
|
|
a3abe7456e | ||
|
|
14c6b8d41f | ||
|
|
ea709912d4 | ||
|
|
cb5658d7dc | ||
|
|
766523cf8c | ||
|
|
0cca086aab | ||
|
|
7b1cad223d | ||
|
|
e80fd4fc78 | ||
|
|
88d723736f | ||
|
|
dc33d5db82 | ||
|
|
7ffc60fb2c | ||
|
|
010b18be34 | ||
|
|
4209d7733b | ||
|
|
ff324ab2fb | ||
|
|
1d69e631b5 | ||
|
|
08d8304a52 | ||
|
|
b2a3071658 | ||
|
|
e55a4dcab1 | ||
|
|
540ffe116e | ||
|
|
3b3766fbe0 | ||
|
|
3f8cfa3b0a | ||
|
|
06530ebfa1 | ||
|
|
fd7330ea77 | ||
|
|
ba0b98ef32 | ||
|
|
d539acf2ff | ||
|
|
5018b91f6c | ||
|
|
b73cc32ffb | ||
|
|
937d0d731d | ||
|
|
bac9af50df | ||
|
|
0da09ba47e | ||
|
|
cd7f2eb73e | ||
|
|
8b01c132c1 | ||
|
|
ed6a65156c | ||
|
|
5a7e506c38 | ||
|
|
9514b0f100 | ||
|
|
fdc1b6ea9e | ||
|
|
174837dddf | ||
|
|
c9a31aab15 | ||
|
|
606a1b57e6 | ||
|
|
f6a23492fa | ||
|
|
414ea6fd8c | ||
|
|
fa220c5c25 | ||
|
|
bf5633fa43 | ||
|
|
97426911a3 | ||
|
|
561c9a77d8 | ||
|
|
0e3f7bc63a | ||
|
|
405d323709 | ||
|
|
5dd8dfb4dc | ||
|
|
a84d92be4d | ||
|
|
3a61a0de2e | ||
|
|
6ebdf0580b | ||
|
|
c6bff8ae18 | ||
|
|
a6244eea28 | ||
|
|
46beae9061 | ||
|
|
3f53ed5d5c | ||
|
|
d8030ed9e7 | ||
|
|
b9b6ed33ee | ||
|
|
ac85a3ce64 | ||
|
|
bd43f0393c | ||
|
|
dc0c41982f | ||
|
|
f2706aa8c0 | ||
|
|
ff877b8144 | ||
|
|
d3db4da11a | ||
|
|
7cd68381f1 | ||
|
|
68ccb96089 | ||
|
|
1dd9e705f2 | ||
|
|
323d4a0e1b | ||
|
|
c01f7d75d5 | ||
|
|
4e2de2479a | ||
|
|
29cbd9d469 | ||
|
|
dfdd037878 | ||
|
|
6540ba6239 | ||
|
|
996544da2d | ||
|
|
2169b70874 | ||
|
|
4fcf8280f6 | ||
|
|
19f33d205c | ||
|
|
ba7ad1029c | ||
|
|
f19c542d6d | ||
|
|
4604694255 | ||
|
|
5ae593672e | ||
|
|
4261595078 | ||
|
|
0c29b68cf8 | ||
|
|
ae295f1bf5 | ||
|
|
d203cb0658 | ||
|
|
681735b94c | ||
|
|
e56357b4f2 | ||
|
|
46f2abc38c | ||
|
|
4a4dabaaa5 | ||
|
|
104d236646 | ||
|
|
c303d6e717 | ||
|
|
235abb0c10 | ||
|
|
fef78939e1 | ||
|
|
099e7e0637 | ||
|
|
b071affcb4 | ||
|
|
5e63a44e71 | ||
|
|
332cf3cd2d | ||
|
|
eb0f8f9542 | ||
|
|
06c6ddb2d6 | ||
|
|
8e5b6ff185 | ||
|
|
380244fa7b | ||
|
|
9d3dde60ff | ||
|
|
519d15428c | ||
|
|
b277c28ed7 | ||
|
|
c7c8887719 | ||
|
|
06aa92b0b6 | ||
|
|
cd42555238 | ||
|
|
113ccfe6af | ||
|
|
d3d2e25090 | ||
|
|
d6df26465f | ||
|
|
85dac3d47e | ||
|
|
41fd1a24bb | ||
|
|
47048e4df4 | ||
|
|
8ccb008834 | ||
|
|
c4855909fa | ||
|
|
8b97271c26 | ||
|
|
8fb9b45e42 | ||
|
|
a27d483009 | ||
|
|
ef6fd78ede | ||
|
|
1b37d9cbc6 | ||
|
|
05416f56aa | ||
|
|
148035c8ca | ||
|
|
ce35324e73 | ||
|
|
8dfb0cb4e7 | ||
|
|
3875fc5953 | ||
|
|
df6892b908 | ||
|
|
3d63d4fb36 | ||
|
|
a70cb8af78 | ||
|
|
a2a4361d6e | ||
|
|
f5e3344bfc | ||
|
|
2e81be7721 | ||
|
|
56e90dd30b | ||
|
|
89360516d7 | ||
|
|
9cbb684d50 | ||
|
|
43fe351f1b | ||
|
|
59aba0bc75 | ||
|
|
a7158fee67 | ||
|
|
cbe9eda0a8 | ||
|
|
89ab78371f | ||
|
|
e4fd5100c4 | ||
|
|
87d7c024bf | ||
|
|
e7b2d4672c | ||
|
|
b590e51f88 | ||
|
|
98d5c415b3 | ||
|
|
c79b741971 | ||
|
|
c933a49c71 | ||
|
|
18b3ffbf99 | ||
|
|
54516ee939 | ||
|
|
82b9eae882 | ||
|
|
edb5c7d2c8 | ||
|
|
3ff0218326 | ||
|
|
db83c78478 | ||
|
|
25639ccf25 | ||
|
|
6ba06c0f53 | ||
|
|
d0a86b3cd2 | ||
|
|
f0993ca4a8 | ||
|
|
bb84484096 | ||
|
|
ce04480e60 | ||
|
|
a90654bd63 | ||
|
|
3aafa0cf49 | ||
|
|
f6cb2833ca | ||
|
|
809f101f55 | ||
|
|
a1a887ddac | ||
|
|
b5c5531180 | ||
|
|
f975d30258 | ||
|
|
6422040262 | ||
|
|
2ee5ac02cf | ||
|
|
b09aaba421 | ||
|
|
110d9232cd | ||
|
|
ebc8fba5bf | ||
|
|
dd6725b80a | ||
|
|
a7da8673bf | ||
|
|
560fbd1a0e | ||
|
|
02a0b8b649 | ||
|
|
e02574c6d9 | ||
|
|
1536936177 | ||
|
|
b3fec4c401 | ||
|
|
b082764e30 | ||
|
|
fde829c4f0 | ||
|
|
e3bd63934b | ||
|
|
035e96a79b | ||
|
|
18840c8af5 | ||
|
|
8d0e54d776 | ||
|
|
4e1359e2cc | ||
|
|
5f5f1343cd | ||
|
|
402a40c108 | ||
|
|
6c3baf03aa | ||
|
|
dd57d7d77f | ||
|
|
1c0ece6ac1 | ||
|
|
14e5001d0c | ||
|
|
0768ed453d | ||
|
|
ab37f59345 | ||
|
|
b5387ed769 | ||
|
|
f851877449 | ||
|
|
227d8b69a7 | ||
|
|
07f677e9e8 | ||
|
|
810b2a2bd6 | ||
|
|
b62c0dcb32 | ||
|
|
269e414e84 | ||
|
|
02c7261b74 | ||
|
|
b7a02d9465 | ||
|
|
c530e965f8 | ||
|
|
c2072cc92b | ||
|
|
2a1a6301a9 | ||
|
|
fe437cc9b7 | ||
|
|
ec03631339 | ||
|
|
0ca4e81e13 | ||
|
|
08ee73d671 | ||
|
|
373347950c | ||
|
|
4e793a51ba | ||
|
|
d91f5b77c8 | ||
|
|
5bd9c5aee8 | ||
|
|
40ed44cbea | ||
|
|
30a5df5895 | ||
|
|
737a1fd9fa | ||
|
|
57114c1a55 | ||
|
|
936b271448 | ||
|
|
84119eefaa | ||
|
|
e7e940afa5 | ||
|
|
a4c6cd2fbe | ||
|
|
5919082a53 | ||
|
|
820a782f94 | ||
|
|
8581db1da7 | ||
|
|
98052646f2 | ||
|
|
08c2bd82bd | ||
|
|
3bebbf7970 | ||
|
|
c24c6b38b1 | ||
|
|
d9b326dd48 | ||
|
|
d56a487169 | ||
|
|
64bfa20f6a | ||
|
|
6f67ae1dfc | ||
|
|
2464322dc5 | ||
|
|
da11cef29a | ||
|
|
255c3c5b9a | ||
|
|
647a023776 | ||
|
|
810e29f1ef | ||
|
|
1288085b31 | ||
|
|
9211ba8371 | ||
|
|
72917f1d2c | ||
|
|
c0ea1a38a6 | ||
|
|
a58301a97d | ||
|
|
c78c159d72 | ||
|
|
7094b7e62f | ||
|
|
57b63db567 | ||
|
|
273e9b287f | ||
|
|
7655b84494 | ||
|
|
43595f7e17 | ||
|
|
3970639c34 | ||
|
|
7eae3691c2 | ||
|
|
73a0197cac | ||
|
|
2c171e30fa | ||
|
|
11cdf542ac | ||
|
|
4f842014ee | ||
|
|
269fa14721 | ||
|
|
dc559f2439 | ||
|
|
f80d522c6a | ||
|
|
24d2d62121 | ||
|
|
7656ca8313 | ||
|
|
8ef87205f9 | ||
|
|
9a9fea4423 | ||
|
|
768b98ae77 | ||
|
|
0caeeb56c5 | ||
|
|
1bfd8b1a76 | ||
|
|
3899c9e6d3 | ||
|
|
e3d250a623 | ||
|
|
382b5d5073 | ||
|
|
25a5ebe0c7 | ||
|
|
dc22726425 | ||
|
|
2723ca0b85 | ||
|
|
f05b4a0ca0 | ||
|
|
1bdd93cc77 | ||
|
|
f311d53c60 | ||
|
|
ce03157f16 | ||
|
|
105b1b9d58 | ||
|
|
8bcccb17f9 | ||
|
|
5fa3b90b2c | ||
|
|
781e4571b2 | ||
|
|
7573dc34aa | ||
|
|
de700e7859 | ||
|
|
d38e8e213a | ||
|
|
f5c6a6be3a | ||
|
|
57fd84e20c | ||
|
|
fdb7a23171 | ||
|
|
407da8c4b8 | ||
|
|
3a57f4363f | ||
|
|
42d7f2a3b2 | ||
|
|
0cf922cc4e | ||
|
|
48c5aab5ee | ||
|
|
bef512c425 | ||
|
|
555e9c6762 | ||
|
|
f721b9e3df | ||
|
|
42533ebbb3 | ||
|
|
ab6e92f996 | ||
|
|
721741281e | ||
|
|
e2fe1a1c5d | ||
|
|
1555f40bad | ||
|
|
0028dc46e6 | ||
|
|
e4bd53b395 | ||
|
|
fe68c15a4a | ||
|
|
6127a9a041 |
@@ -52,6 +52,7 @@ components: &components
|
||||
- homeassistant/components/auth/**
|
||||
- homeassistant/components/automation/**
|
||||
- homeassistant/components/backup/**
|
||||
- homeassistant/components/bluetooth/**
|
||||
- homeassistant/components/cloud/**
|
||||
- homeassistant/components/config/**
|
||||
- homeassistant/components/configurator/**
|
||||
@@ -87,6 +88,7 @@ components: &components
|
||||
- homeassistant/components/persistent_notification/**
|
||||
- homeassistant/components/person/**
|
||||
- homeassistant/components/recorder/**
|
||||
- homeassistant/components/repairs/**
|
||||
- homeassistant/components/safe_mode/**
|
||||
- homeassistant/components/script/**
|
||||
- homeassistant/components/shopping_list/**
|
||||
|
||||
17
.coveragerc
17
.coveragerc
@@ -23,6 +23,7 @@ omit =
|
||||
homeassistant/components/adax/climate.py
|
||||
homeassistant/components/adguard/__init__.py
|
||||
homeassistant/components/adguard/const.py
|
||||
homeassistant/components/adguard/entity.py
|
||||
homeassistant/components/adguard/sensor.py
|
||||
homeassistant/components/adguard/switch.py
|
||||
homeassistant/components/ads/*
|
||||
@@ -136,6 +137,7 @@ omit =
|
||||
homeassistant/components/bosch_shc/switch.py
|
||||
homeassistant/components/braviatv/__init__.py
|
||||
homeassistant/components/braviatv/const.py
|
||||
homeassistant/components/braviatv/entity.py
|
||||
homeassistant/components/braviatv/media_player.py
|
||||
homeassistant/components/braviatv/remote.py
|
||||
homeassistant/components/broadlink/__init__.py
|
||||
@@ -210,7 +212,6 @@ omit =
|
||||
homeassistant/components/denonavr/media_player.py
|
||||
homeassistant/components/denonavr/receiver.py
|
||||
homeassistant/components/deutsche_bahn/sensor.py
|
||||
homeassistant/components/devolo_home_control/light.py
|
||||
homeassistant/components/devolo_home_control/sensor.py
|
||||
homeassistant/components/devolo_home_control/switch.py
|
||||
homeassistant/components/digital_ocean/*
|
||||
@@ -267,6 +268,7 @@ omit =
|
||||
homeassistant/components/eliqonline/sensor.py
|
||||
homeassistant/components/elkm1/__init__.py
|
||||
homeassistant/components/elkm1/alarm_control_panel.py
|
||||
homeassistant/components/elkm1/binary_sensor.py
|
||||
homeassistant/components/elkm1/climate.py
|
||||
homeassistant/components/elkm1/discovery.py
|
||||
homeassistant/components/elkm1/light.py
|
||||
@@ -276,6 +278,7 @@ omit =
|
||||
homeassistant/components/elmax/__init__.py
|
||||
homeassistant/components/elmax/common.py
|
||||
homeassistant/components/elmax/const.py
|
||||
homeassistant/components/elmax/binary_sensor.py
|
||||
homeassistant/components/elmax/switch.py
|
||||
homeassistant/components/elv/*
|
||||
homeassistant/components/emby/media_player.py
|
||||
@@ -385,6 +388,7 @@ omit =
|
||||
homeassistant/components/flume/__init__.py
|
||||
homeassistant/components/flume/sensor.py
|
||||
homeassistant/components/flunearyou/__init__.py
|
||||
homeassistant/components/flunearyou/repairs.py
|
||||
homeassistant/components/flunearyou/sensor.py
|
||||
homeassistant/components/folder/sensor.py
|
||||
homeassistant/components/folder_watcher/*
|
||||
@@ -439,7 +443,6 @@ omit =
|
||||
homeassistant/components/google_cloud/tts.py
|
||||
homeassistant/components/google_maps/device_tracker.py
|
||||
homeassistant/components/google_pubsub/__init__.py
|
||||
homeassistant/components/gpmdp/media_player.py
|
||||
homeassistant/components/gpsd/sensor.py
|
||||
homeassistant/components/greenwave/light.py
|
||||
homeassistant/components/group/notify.py
|
||||
@@ -449,6 +452,7 @@ omit =
|
||||
homeassistant/components/gtfs/sensor.py
|
||||
homeassistant/components/guardian/__init__.py
|
||||
homeassistant/components/guardian/binary_sensor.py
|
||||
homeassistant/components/guardian/button.py
|
||||
homeassistant/components/guardian/sensor.py
|
||||
homeassistant/components/guardian/switch.py
|
||||
homeassistant/components/guardian/util.py
|
||||
@@ -555,6 +559,7 @@ omit =
|
||||
homeassistant/components/insteon/utils.py
|
||||
homeassistant/components/intellifire/__init__.py
|
||||
homeassistant/components/intellifire/coordinator.py
|
||||
homeassistant/components/intellifire/climate.py
|
||||
homeassistant/components/intellifire/binary_sensor.py
|
||||
homeassistant/components/intellifire/sensor.py
|
||||
homeassistant/components/intellifire/switch.py
|
||||
@@ -642,9 +647,6 @@ omit =
|
||||
homeassistant/components/life360/const.py
|
||||
homeassistant/components/life360/coordinator.py
|
||||
homeassistant/components/life360/device_tracker.py
|
||||
homeassistant/components/lifx/__init__.py
|
||||
homeassistant/components/lifx/const.py
|
||||
homeassistant/components/lifx/light.py
|
||||
homeassistant/components/lifx_cloud/scene.py
|
||||
homeassistant/components/lightwave/*
|
||||
homeassistant/components/limitlessled/light.py
|
||||
@@ -716,7 +718,6 @@ omit =
|
||||
homeassistant/components/microsoft/tts.py
|
||||
homeassistant/components/miflora/sensor.py
|
||||
homeassistant/components/mikrotik/hub.py
|
||||
homeassistant/components/mikrotik/device_tracker.py
|
||||
homeassistant/components/mill/climate.py
|
||||
homeassistant/components/mill/const.py
|
||||
homeassistant/components/mill/sensor.py
|
||||
@@ -927,7 +928,6 @@ omit =
|
||||
homeassistant/components/plex/cast.py
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/view.py
|
||||
homeassistant/components/plugwise/select.py
|
||||
homeassistant/components/plum_lightpad/light.py
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
homeassistant/components/point/__init__.py
|
||||
@@ -1109,7 +1109,6 @@ omit =
|
||||
homeassistant/components/smtp/notify.py
|
||||
homeassistant/components/snapcast/*
|
||||
homeassistant/components/snmp/*
|
||||
homeassistant/components/sochain/sensor.py
|
||||
homeassistant/components/solaredge/__init__.py
|
||||
homeassistant/components/solaredge/coordinator.py
|
||||
homeassistant/components/solaredge/sensor.py
|
||||
@@ -1494,6 +1493,7 @@ omit =
|
||||
homeassistant/components/yolink/climate.py
|
||||
homeassistant/components/yolink/const.py
|
||||
homeassistant/components/yolink/coordinator.py
|
||||
homeassistant/components/yolink/cover.py
|
||||
homeassistant/components/yolink/entity.py
|
||||
homeassistant/components/yolink/lock.py
|
||||
homeassistant/components/yolink/sensor.py
|
||||
@@ -1521,7 +1521,6 @@ omit =
|
||||
homeassistant/components/zha/light.py
|
||||
homeassistant/components/zha/sensor.py
|
||||
homeassistant/components/zhong_hong/climate.py
|
||||
homeassistant/components/xbee/*
|
||||
homeassistant/components/ziggo_mediabox_xl/media_player.py
|
||||
homeassistant/components/zoneminder/*
|
||||
homeassistant/components/supla/*
|
||||
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -33,6 +33,7 @@
|
||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
||||
- [ ] New integration (thank you!)
|
||||
- [ ] New feature (which adds functionality to an existing integration)
|
||||
- [ ] Deprecation (breaking change to happen in the future)
|
||||
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
||||
- [ ] Code quality improvements to existing code or addition of tests
|
||||
|
||||
|
||||
36
.github/workflows/builder.yml
vendored
36
.github/workflows/builder.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -102,9 +102,20 @@ jobs:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
repo: home-assistant/frontend
|
||||
branch: dev
|
||||
workflow: nightly.yaml
|
||||
workflow_conclusion: success
|
||||
name: wheels
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -112,10 +123,23 @@ jobs:
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
shell: bash
|
||||
run: |
|
||||
python3 -m pip install packaging
|
||||
python3 -m pip install packaging tomli
|
||||
python3 -m pip install --use-deprecated=legacy-resolver .
|
||||
version="$(python3 script/version_bump.py nightly)"
|
||||
|
||||
if [[ "$(ls home_assistant_frontend*.whl)" =~ ^home_assistant_frontend-(.*)-py3-none-any.whl$ ]]; then
|
||||
echo "Found frontend wheel, setting version to: ${BASH_REMATCH[1]}"
|
||||
frontend_version="${BASH_REMATCH[1]}" yq \
|
||||
--inplace e -o json \
|
||||
'.requirements = ["home-assistant-frontend=="+env(frontend_version)]' \
|
||||
homeassistant/components/frontend/manifest.json
|
||||
|
||||
sed -i "s|home-assistant-frontend==.*|home-assistant-frontend==${BASH_REMATCH[1]}|" \
|
||||
homeassistant/package_constraints.txt
|
||||
|
||||
python -m script.gen_requirements_all
|
||||
fi
|
||||
|
||||
- name: Write meta info file
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -135,7 +159,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2022.06.2
|
||||
uses: home-assistant/builder@2022.07.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -201,7 +225,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2022.06.2
|
||||
uses: home-assistant/builder@2022.07.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
|
||||
512
.github/workflows/ci.yaml
vendored
512
.github/workflows/ci.yaml
vendored
@@ -20,9 +20,9 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 10
|
||||
PIP_CACHE_VERSION: 4
|
||||
HA_SHORT_VERSION: 2022.7
|
||||
CACHE_VERSION: 1
|
||||
PIP_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: 2022.8
|
||||
DEFAULT_PYTHON: 3.9
|
||||
PRE_COMMIT_CACHE: ~/.cache/pre-commit
|
||||
PIP_CACHE: /tmp/pip-cache
|
||||
@@ -35,24 +35,38 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
name: Determine what has changed
|
||||
info:
|
||||
name: Collect information & changes data
|
||||
outputs:
|
||||
# In case of issues with the partial run, use the following line instead:
|
||||
# test_full_suite: 'true'
|
||||
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
|
||||
core: ${{ steps.core.outputs.changes }}
|
||||
integrations: ${{ steps.integrations.outputs.changes }}
|
||||
integrations_glob: ${{ steps.info.outputs.integrations_glob }}
|
||||
tests: ${{ steps.info.outputs.tests }}
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
||||
integrations: ${{ steps.integrations.outputs.changes }}
|
||||
pre-commit_cache_key: ${{ steps.generate_pre-commit_cache_key.outputs.key }}
|
||||
python_cache_key: ${{ steps.generate_python_cache_key.outputs.key }}
|
||||
requirements: ${{ steps.core.outputs.requirements }}
|
||||
runs-on: ubuntu-latest
|
||||
test_full_suite: ${{ steps.info.outputs.test_full_suite }}
|
||||
test_group_count: ${{ steps.info.outputs.test_group_count }}
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
tests: ${{ steps.info.outputs.tests }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate_python_cache_key
|
||||
run: >-
|
||||
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
|
||||
hashFiles('requirements_test.txt') }}-${{
|
||||
hashFiles('requirements_all.txt') }}-${{
|
||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||
- name: Generate partial pre-commit restore key
|
||||
id: generate_pre-commit_cache_key
|
||||
run: >-
|
||||
echo "::set-output name=key::${{ env.CACHE_VERSION }}-${{ env.DEFAULT_PYTHON }}-${{
|
||||
hashFiles('.pre-commit-config.yaml') }}"
|
||||
- name: Filter for core changes
|
||||
uses: dorny/paths-filter@v2.10.2
|
||||
id: core
|
||||
@@ -79,8 +93,8 @@ jobs:
|
||||
# Defaults
|
||||
integrations_glob=""
|
||||
test_full_suite="true"
|
||||
test_groups="[1, 2, 3, 4, 5, 6]"
|
||||
test_group_count=6
|
||||
test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||
test_group_count=10
|
||||
tests="[]"
|
||||
tests_glob=""
|
||||
|
||||
@@ -123,8 +137,8 @@ jobs:
|
||||
|| [[ "${{ github.event.inputs.full }}" == "true" ]] \
|
||||
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-full-run') }}" == "true" ]];
|
||||
then
|
||||
test_groups="[1, 2, 3, 4, 5, 6]"
|
||||
test_group_count=6
|
||||
test_groups="[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"
|
||||
test_group_count=10
|
||||
test_full_suite="true"
|
||||
fi
|
||||
|
||||
@@ -142,84 +156,39 @@ jobs:
|
||||
echo "tests_glob: ${tests_glob}"
|
||||
echo "::set-output name=tests_glob::${tests_glob}"
|
||||
|
||||
# Separate job to pre-populate the base dependency cache
|
||||
# This prevent upcoming jobs to do the same individually
|
||||
prepare-base:
|
||||
name: Prepare base dependencies
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
||||
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
|
||||
pre-commit:
|
||||
name: Prepare pre-commit base
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- info
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate-python-key
|
||||
run: >-
|
||||
echo "::set-output name=key::base-venv-${{ env.CACHE_VERSION }}-${{
|
||||
hashFiles('requirements.txt') }}-${{
|
||||
hashFiles('requirements_test.txt') }}-${{
|
||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||
- name: Generate partial pip restore key
|
||||
id: generate-pip-key
|
||||
run: >-
|
||||
echo "::set-output name=key::base-pip-${{ env.PIP_CACHE_VERSION }}-${{
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
||||
cache: "pip"
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
steps.generate-python-key.outputs.key }}
|
||||
# Temporary disabling the restore of environments when bumping
|
||||
# a dependency. It seems that we are experiencing issues with
|
||||
# restoring environments in GitHub Actions, although unclear why.
|
||||
# First attempt: https://github.com/home-assistant/core/pull/62383
|
||||
#
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements_test.txt') }}-
|
||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}-
|
||||
# ${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-venv-${{ env.CACHE_VERSION }}-
|
||||
- name: Restore pip wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3.0.4
|
||||
with:
|
||||
path: ${{ env.PIP_CACHE }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
steps.generate-pip-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-base-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
||||
key: ${{ runner.os }}-venv-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements.txt -r requirements_test.txt --use-deprecated=legacy-resolver
|
||||
- name: Generate partial pre-commit restore key
|
||||
id: generate-pre-commit-key
|
||||
run: >-
|
||||
echo "::set-output name=key::pre-commit-${{ env.CACHE_VERSION }}-${{
|
||||
hashFiles('.pre-commit-config.yaml') }}"
|
||||
pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.generate-pre-commit-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pre-commit-${{ env.CACHE_VERSION }}-
|
||||
key: ${{ runner.os }}-pre-commit-${{ needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Install pre-commit dependencies
|
||||
if: steps.cache-precommit.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -228,25 +197,24 @@ jobs:
|
||||
|
||||
lint-black:
|
||||
name: Check black
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- prepare-base
|
||||
- info
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.prepare-base.outputs.python-key }}
|
||||
key: ${{ runner.os }}-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: |
|
||||
@@ -254,49 +222,48 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
key: ${{ runner.os }}-pre-commit-${{ 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.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual black --all-files --show-diff-on-failure
|
||||
- name: Run black (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
pre-commit run --hook-stage manual black --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
|
||||
lint-flake8:
|
||||
name: Check flake8
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- prepare-base
|
||||
- info
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.prepare-base.outputs.python-key }}
|
||||
key: ${{ runner.os }}-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: |
|
||||
@@ -304,10 +271,10 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
key: ${{ runner.os }}-pre-commit-${{ 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: |
|
||||
@@ -317,37 +284,38 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/flake8.json"
|
||||
- name: Run flake8 (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual flake8 --all-files
|
||||
- name: Run flake8 (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/*
|
||||
pre-commit run --hook-stage manual flake8 --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
|
||||
|
||||
lint-isort:
|
||||
name: Check isort
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare-base
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- info
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.prepare-base.outputs.python-key }}
|
||||
key: ${{ runner.os }}-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: |
|
||||
@@ -355,10 +323,10 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
key: ${{ runner.os }}-pre-commit-${{ 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: |
|
||||
@@ -371,25 +339,24 @@ jobs:
|
||||
|
||||
lint-other:
|
||||
name: Check other linters
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- prepare-base
|
||||
- info
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.prepare-base.outputs.python-key }}
|
||||
key: ${{ runner.os }}-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: |
|
||||
@@ -397,10 +364,10 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
|
||||
key: ${{ runner.os }}-pre-commit-${{ 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: |
|
||||
@@ -408,17 +375,17 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Run pyupgrade (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual pyupgrade --all-files --show-diff-on-failure
|
||||
- name: Run pyupgrade (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
pre-commit run --hook-stage manual pyupgrade --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
|
||||
- name: Register yamllint problem matcher
|
||||
run: |
|
||||
@@ -437,17 +404,17 @@ jobs:
|
||||
pre-commit run --hook-stage manual check-json --all-files
|
||||
|
||||
- name: Run prettier (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual prettier --all-files
|
||||
|
||||
- name: Run prettier (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/*
|
||||
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/*
|
||||
|
||||
- name: Register check executables problem matcher
|
||||
run: |
|
||||
@@ -478,36 +445,105 @@ jobs:
|
||||
args: hadolint Dockerfile.dev
|
||||
|
||||
- name: Run bandit (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pre-commit run --hook-stage manual bandit --all-files --show-diff-on-failure
|
||||
- name: Run bandit (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
shopt -s globstar
|
||||
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.changes.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
pre-commit run --hook-stage manual bandit --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/**/* --show-diff-on-failure
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare-tests
|
||||
base:
|
||||
name: Prepare dependencies
|
||||
runs-on: ubuntu-20.04
|
||||
needs: info
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
python-version: ["3.9", "3.10"]
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Generate partial pip restore key
|
||||
id: generate-pip-key
|
||||
run: >-
|
||||
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
needs.prepare-tests.outputs.python-key }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore pip wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: ${{ env.PIP_CACHE }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
steps.generate-pip-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
||||
- name: Install additional OS dependencies
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libavcodec-dev \
|
||||
libavdevice-dev \
|
||||
libavfilter-dev \
|
||||
libavformat-dev \
|
||||
libavutil-dev \
|
||||
libswresample-dev \
|
||||
libswscale-dev \
|
||||
libudev-dev
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.3" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
|
||||
pip install -e .
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
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: |
|
||||
@@ -520,23 +556,26 @@ jobs:
|
||||
|
||||
gen-requirements-all:
|
||||
name: Check all requirements
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare-base
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.prepare-base.outputs.python-key }}
|
||||
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: |
|
||||
@@ -547,94 +586,29 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.gen_requirements_all validate
|
||||
|
||||
prepare-tests:
|
||||
name: Prepare tests for Python ${{ matrix.python-version }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.9", "3.10"]
|
||||
outputs:
|
||||
python-key: ${{ steps.generate-python-key.outputs.key }}
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate-python-key
|
||||
run: >-
|
||||
echo "::set-output name=key::venv-${{ env.CACHE_VERSION }}-${{
|
||||
hashFiles('requirements_test.txt') }}-${{
|
||||
hashFiles('requirements_all.txt') }}-${{
|
||||
hashFiles('homeassistant/package_constraints.txt') }}"
|
||||
- name: Generate partial pip restore key
|
||||
id: generate-pip-key
|
||||
run: >-
|
||||
echo "::set-output name=key::pip-${{ env.PIP_CACHE_VERSION }}-${{
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')"
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
steps.generate-python-key.outputs.key }}
|
||||
# Temporary disabling the restore of environments when bumping
|
||||
# a dependency. It seems that we are experiencing issues with
|
||||
# restoring environments in GitHub Actions, although unclear why.
|
||||
# First attempt: https://github.com/home-assistant/core/pull/62383
|
||||
#
|
||||
# restore-keys: |
|
||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-${{ hashFiles('requirements_all.txt') }}-
|
||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-${{ hashFiles('requirements_test.txt') }}-
|
||||
# ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-
|
||||
- name: Restore pip wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v3.0.4
|
||||
with:
|
||||
path: ${{ env.PIP_CACHE }}
|
||||
key: >-
|
||||
${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
steps.generate-pip-key.outputs.key }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.python-version }}-pip-${{ env.PIP_CACHE_VERSION }}-${{ env.HA_SHORT_VERSION }}-
|
||||
- name: Create full Python ${{ matrix.python-version }} virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# Temporary addition of cmake, needed to build some Python 3.9 packages
|
||||
apt-get update
|
||||
apt-get -y install cmake
|
||||
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<22.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt --use-deprecated=legacy-resolver
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt --use-deprecated=legacy-resolver
|
||||
pip install -e .
|
||||
|
||||
pylint:
|
||||
name: Check pylint
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 20
|
||||
needs:
|
||||
- changes
|
||||
- prepare-tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
needs.prepare-tests.outputs.python-key }}
|
||||
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: |
|
||||
@@ -644,39 +618,41 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/pylint.json"
|
||||
- name: Run pylint (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint homeassistant
|
||||
pylint --ignore-missing-annotations=y homeassistant
|
||||
- name: Run pylint (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
|
||||
pylint --ignore-missing-annotations=y homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||
|
||||
mypy:
|
||||
name: Check mypy
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- prepare-tests
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
needs.prepare-tests.outputs.python-key }}
|
||||
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: |
|
||||
@@ -686,41 +662,45 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/mypy.json"
|
||||
- name: Run mypy (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
mypy homeassistant pylint
|
||||
- name: Run mypy (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
mypy homeassistant/components/${{ needs.changes.outputs.integrations_glob }}
|
||||
mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||
|
||||
pip-check:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.changes.outputs.requirements == 'true' || github.event.inputs.full == 'true'
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- prepare-tests
|
||||
- info
|
||||
- base
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.9]
|
||||
python-version: ["3.9", "3.10"]
|
||||
name: Run pip check ${{ matrix.python-version }}
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
needs.prepare-tests.outputs.python-key }}
|
||||
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: |
|
||||
@@ -732,38 +712,48 @@ jobs:
|
||||
./script/pip_check $PIP_CACHE
|
||||
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& (needs.changes.outputs.test_full_suite == 'true' || needs.changes.outputs.tests_glob)
|
||||
&& (needs.info.outputs.test_full_suite == 'true' || needs.info.outputs.tests_glob)
|
||||
needs:
|
||||
- changes
|
||||
- info
|
||||
- base
|
||||
- gen-requirements-all
|
||||
- hassfest
|
||||
- lint-black
|
||||
- lint-other
|
||||
- lint-isort
|
||||
- mypy
|
||||
- prepare-tests
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
group: ${{ fromJson(needs.changes.outputs.test_groups) }}
|
||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||
python-version: ["3.9", "3.10"]
|
||||
name: >-
|
||||
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
|
||||
container: homeassistant/ci-azure:${{ matrix.python-version }}
|
||||
steps:
|
||||
- name: Install additional OS dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install \
|
||||
bluez \
|
||||
ffmpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v3.0.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v3.0.4
|
||||
uses: actions/cache@v3.0.5
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ matrix.python-version }}-${{
|
||||
needs.prepare-tests.outputs.python-key }}
|
||||
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: |
|
||||
@@ -783,7 +773,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||
- name: Run pytest (fully)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -794,7 +784,7 @@ jobs:
|
||||
--durations=10 \
|
||||
-n auto \
|
||||
--dist=loadfile \
|
||||
--test-group-count ${{ needs.changes.outputs.test_group_count }} \
|
||||
--test-group-count ${{ needs.info.outputs.test_group_count }} \
|
||||
--test-group=${{ matrix.group }} \
|
||||
--cov="homeassistant" \
|
||||
--cov-report=xml \
|
||||
@@ -802,8 +792,8 @@ jobs:
|
||||
-p no:sugar \
|
||||
tests
|
||||
- name: Run pytest (partially)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
timeout-minutes: 20
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
timeout-minutes: 10
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -838,9 +828,9 @@ jobs:
|
||||
|
||||
coverage:
|
||||
name: Upload test coverage to Codecov
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
needs:
|
||||
- changes
|
||||
- info
|
||||
- pytest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
@@ -848,10 +838,10 @@ jobs:
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
- name: Upload coverage to Codecov (full coverage)
|
||||
if: needs.changes.outputs.test_full_suite == 'true'
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
uses: codecov/codecov-action@v3.1.0
|
||||
with:
|
||||
flags: full-suite
|
||||
- name: Upload coverage to Codecov (partial coverage)
|
||||
if: needs.changes.outputs.test_full_suite == 'false'
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
uses: codecov/codecov-action@v3.1.0
|
||||
|
||||
4
.github/workflows/translations.yaml
vendored
4
.github/workflows/translations.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
uses: actions/checkout@v3.0.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v4.0.0
|
||||
uses: actions/setup-python@v4.1.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
||||
3
.github/workflows/wheels.yml
vendored
3
.github/workflows/wheels.yml
vendored
@@ -157,6 +157,9 @@ jobs:
|
||||
echo "cmake==3.22.2"
|
||||
) >> homeassistant/package_constraints.txt
|
||||
|
||||
# Do not pin numpy in wheels building
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2022.06.7
|
||||
with:
|
||||
|
||||
6
.ignore
6
.ignore
@@ -1,6 +0,0 @@
|
||||
# Patterns matched in this file will be ignored by supported search utilities
|
||||
|
||||
# Ignore generated html and javascript files
|
||||
/homeassistant/components/frontend/www_static/*.html
|
||||
/homeassistant/components/frontend/www_static/*.js
|
||||
/homeassistant/components/frontend/www_static/panels/*.html
|
||||
@@ -1,11 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.34.0
|
||||
rev: v2.37.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -21,7 +21,7 @@ repos:
|
||||
- --skip="./.*,*.csv,*.json"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json]
|
||||
exclude: ^tests/fixtures/
|
||||
exclude: ^tests/fixtures/|homeassistant/generated/
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 4.0.1
|
||||
hooks:
|
||||
@@ -31,8 +31,8 @@ repos:
|
||||
- pyflakes==2.4.0
|
||||
- flake8-docstrings==1.6.0
|
||||
- pydocstyle==6.1.1
|
||||
- flake8-comprehensions==3.8.0
|
||||
- flake8-noqa==1.2.1
|
||||
- flake8-comprehensions==3.10.0
|
||||
- flake8-noqa==1.2.5
|
||||
- mccabe==0.6.1
|
||||
files: ^(homeassistant|script|tests)/.+\.py$
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
@@ -61,7 +61,7 @@ repos:
|
||||
- --branch=master
|
||||
- --branch=rc
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.26.3
|
||||
rev: v1.27.1
|
||||
hooks:
|
||||
- id: yamllint
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
@@ -96,7 +96,7 @@ repos:
|
||||
files: ^(homeassistant|pylint)/.+\.py$
|
||||
- id: pylint
|
||||
name: pylint
|
||||
entry: script/run-in-env.sh pylint -j 0
|
||||
entry: script/run-in-env.sh pylint -j 0 --ignore-missing-annotations=y
|
||||
language: script
|
||||
types: [python]
|
||||
files: ^homeassistant/.+\.py$
|
||||
|
||||
@@ -15,12 +15,17 @@ homeassistant.auth.auth_store
|
||||
homeassistant.auth.providers.*
|
||||
homeassistant.helpers.area_registry
|
||||
homeassistant.helpers.condition
|
||||
homeassistant.helpers.debounce
|
||||
homeassistant.helpers.deprecation
|
||||
homeassistant.helpers.discovery
|
||||
homeassistant.helpers.dispatcher
|
||||
homeassistant.helpers.entity
|
||||
homeassistant.helpers.entity_platform
|
||||
homeassistant.helpers.entity_values
|
||||
homeassistant.helpers.event
|
||||
homeassistant.helpers.reload
|
||||
homeassistant.helpers.script_variables
|
||||
homeassistant.helpers.singleton
|
||||
homeassistant.helpers.sun
|
||||
homeassistant.helpers.translation
|
||||
homeassistant.util.async_
|
||||
@@ -57,6 +62,7 @@ homeassistant.components.automation.*
|
||||
homeassistant.components.backup.*
|
||||
homeassistant.components.baf.*
|
||||
homeassistant.components.binary_sensor.*
|
||||
homeassistant.components.bluetooth.*
|
||||
homeassistant.components.bluetooth_tracker.*
|
||||
homeassistant.components.bmw_connected_drive.*
|
||||
homeassistant.components.bond.*
|
||||
@@ -109,6 +115,7 @@ homeassistant.components.group.*
|
||||
homeassistant.components.guardian.*
|
||||
homeassistant.components.history.*
|
||||
homeassistant.components.homeassistant.triggers.event
|
||||
homeassistant.components.homeassistant_alerts.*
|
||||
homeassistant.components.homekit
|
||||
homeassistant.components.homekit.accessories
|
||||
homeassistant.components.homekit.aidmanager
|
||||
@@ -121,6 +128,7 @@ homeassistant.components.homekit.util
|
||||
homeassistant.components.homekit_controller
|
||||
homeassistant.components.homekit_controller.alarm_control_panel
|
||||
homeassistant.components.homekit_controller.button
|
||||
homeassistant.components.homekit_controller.config_flow
|
||||
homeassistant.components.homekit_controller.const
|
||||
homeassistant.components.homekit_controller.lock
|
||||
homeassistant.components.homekit_controller.select
|
||||
@@ -145,6 +153,8 @@ homeassistant.components.lametric.*
|
||||
homeassistant.components.laundrify.*
|
||||
homeassistant.components.lcn.*
|
||||
homeassistant.components.light.*
|
||||
homeassistant.components.lifx.*
|
||||
homeassistant.components.litterrobot.*
|
||||
homeassistant.components.local_ip.*
|
||||
homeassistant.components.lock.*
|
||||
homeassistant.components.logbook.*
|
||||
@@ -153,6 +163,7 @@ homeassistant.components.luftdaten.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.metoffice.*
|
||||
homeassistant.components.mjpeg.*
|
||||
homeassistant.components.modbus.*
|
||||
homeassistant.components.modem_callerid.*
|
||||
@@ -190,6 +201,8 @@ homeassistant.components.recollect_waste.*
|
||||
homeassistant.components.recorder.*
|
||||
homeassistant.components.remote.*
|
||||
homeassistant.components.renault.*
|
||||
homeassistant.components.repairs.*
|
||||
homeassistant.components.rhasspy.*
|
||||
homeassistant.components.ridwell.*
|
||||
homeassistant.components.rituals_perfume_genie.*
|
||||
homeassistant.components.roku.*
|
||||
|
||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
@@ -12,6 +12,14 @@
|
||||
"justMyCode": false,
|
||||
"args": ["--debug", "-c", "config"]
|
||||
},
|
||||
{
|
||||
"name": "Home Assistant (skip pip)",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "homeassistant",
|
||||
"justMyCode": false,
|
||||
"args": ["--debug", "-c", "config", "--skip-pip"]
|
||||
},
|
||||
{
|
||||
// Debug by attaching to local Home Asistant server using Remote Python Debugger.
|
||||
// See https://www.home-assistant.io/integrations/debugpy/
|
||||
|
||||
45
CODEOWNERS
45
CODEOWNERS
@@ -74,6 +74,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/analytics/ @home-assistant/core @ludeeus
|
||||
/homeassistant/components/androidtv/ @JeffLIrion @ollo69
|
||||
/tests/components/androidtv/ @JeffLIrion @ollo69
|
||||
/homeassistant/components/anthemav/ @hyralex
|
||||
/tests/components/anthemav/ @hyralex
|
||||
/homeassistant/components/apache_kafka/ @bachya
|
||||
/tests/components/apache_kafka/ @bachya
|
||||
/homeassistant/components/api/ @home-assistant/core
|
||||
@@ -129,13 +131,15 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/binary_sensor/ @home-assistant/core
|
||||
/tests/components/binary_sensor/ @home-assistant/core
|
||||
/homeassistant/components/bizkaibus/ @UgaitzEtxebarria
|
||||
/homeassistant/components/blebox/ @bbx-a @bbx-jp @riokuu
|
||||
/tests/components/blebox/ @bbx-a @bbx-jp @riokuu
|
||||
/homeassistant/components/blebox/ @bbx-a @riokuu
|
||||
/tests/components/blebox/ @bbx-a @riokuu
|
||||
/homeassistant/components/blink/ @fronzbot
|
||||
/tests/components/blink/ @fronzbot
|
||||
/homeassistant/components/blueprint/ @home-assistant/core
|
||||
/tests/components/blueprint/ @home-assistant/core
|
||||
/homeassistant/components/bluesound/ @thrawnarn
|
||||
/homeassistant/components/bluetooth/ @bdraco
|
||||
/tests/components/bluetooth/ @bdraco
|
||||
/homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe
|
||||
/tests/components/bmw_connected_drive/ @gerard33 @rikroe
|
||||
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
||||
@@ -407,6 +411,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/google_cloud/ @lufton
|
||||
/homeassistant/components/google_travel_time/ @eifinger
|
||||
/tests/components/google_travel_time/ @eifinger
|
||||
/homeassistant/components/govee_ble/ @bdraco
|
||||
/tests/components/govee_ble/ @bdraco
|
||||
/homeassistant/components/gpsd/ @fabaff
|
||||
/homeassistant/components/gree/ @cmroche
|
||||
/tests/components/gree/ @cmroche
|
||||
@@ -449,6 +455,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/home_plus_control/ @chemaaa
|
||||
/homeassistant/components/homeassistant/ @home-assistant/core
|
||||
/tests/components/homeassistant/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
|
||||
/tests/components/homeassistant_alerts/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_yellow/ @home-assistant/core
|
||||
/tests/components/homeassistant_yellow/ @home-assistant/core
|
||||
/homeassistant/components/homekit/ @bdraco
|
||||
@@ -494,6 +502,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/incomfort/ @zxdavb
|
||||
/homeassistant/components/influxdb/ @mdegat01
|
||||
/tests/components/influxdb/ @mdegat01
|
||||
/homeassistant/components/inkbird/ @bdraco
|
||||
/tests/components/inkbird/ @bdraco
|
||||
/homeassistant/components/input_boolean/ @home-assistant/core
|
||||
/tests/components/input_boolean/ @home-assistant/core
|
||||
/homeassistant/components/input_button/ @home-assistant/core
|
||||
@@ -573,7 +583,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/lg_netcast/ @Drafteed
|
||||
/homeassistant/components/life360/ @pnbruckner
|
||||
/tests/components/life360/ @pnbruckner
|
||||
/homeassistant/components/lifx/ @Djelibeybi
|
||||
/homeassistant/components/lifx/ @bdraco @Djelibeybi
|
||||
/tests/components/lifx/ @bdraco @Djelibeybi
|
||||
/homeassistant/components/light/ @home-assistant/core
|
||||
/tests/components/light/ @home-assistant/core
|
||||
/homeassistant/components/linux_battery/ @fabaff
|
||||
@@ -628,8 +639,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/meteoalarm/ @rolfberkenbosch
|
||||
/homeassistant/components/meteoclimatic/ @adrianmo
|
||||
/tests/components/meteoclimatic/ @adrianmo
|
||||
/homeassistant/components/metoffice/ @MrHarcombe
|
||||
/tests/components/metoffice/ @MrHarcombe
|
||||
/homeassistant/components/metoffice/ @MrHarcombe @avee87
|
||||
/tests/components/metoffice/ @MrHarcombe @avee87
|
||||
/homeassistant/components/miflora/ @danielhiversen @basnijholt
|
||||
/homeassistant/components/mikrotik/ @engrbm87
|
||||
/tests/components/mikrotik/ @engrbm87
|
||||
@@ -641,6 +652,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/minecraft_server/ @elmurato
|
||||
/homeassistant/components/minio/ @tkislan
|
||||
/tests/components/minio/ @tkislan
|
||||
/homeassistant/components/moat/ @bdraco
|
||||
/tests/components/moat/ @bdraco
|
||||
/homeassistant/components/mobile_app/ @home-assistant/core
|
||||
/tests/components/mobile_app/ @home-assistant/core
|
||||
/homeassistant/components/modbus/ @adamchengtkc @janiversen @vzahradnik
|
||||
@@ -696,6 +709,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/nextbus/ @vividboarder
|
||||
/tests/components/nextbus/ @vividboarder
|
||||
/homeassistant/components/nextcloud/ @meichthys
|
||||
/homeassistant/components/nextdns/ @bieniu
|
||||
/tests/components/nextdns/ @bieniu
|
||||
/homeassistant/components/nfandroidtv/ @tkdrob
|
||||
/tests/components/nfandroidtv/ @tkdrob
|
||||
/homeassistant/components/nightscout/ @marciogranzotto
|
||||
@@ -852,11 +867,15 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/remote/ @home-assistant/core
|
||||
/homeassistant/components/renault/ @epenet
|
||||
/tests/components/renault/ @epenet
|
||||
/homeassistant/components/repairs/ @home-assistant/core
|
||||
/tests/components/repairs/ @home-assistant/core
|
||||
/homeassistant/components/repetier/ @MTrab @ShadowBr0ther
|
||||
/homeassistant/components/rflink/ @javicalle
|
||||
/tests/components/rflink/ @javicalle
|
||||
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
||||
/tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
|
||||
/homeassistant/components/rhasspy/ @balloob @synesthesiam
|
||||
/tests/components/rhasspy/ @balloob @synesthesiam
|
||||
/homeassistant/components/ridwell/ @bachya
|
||||
/tests/components/ridwell/ @bachya
|
||||
/homeassistant/components/ring/ @balloob
|
||||
@@ -911,6 +930,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/homeassistant/components/sensor/ @home-assistant/core
|
||||
/tests/components/sensor/ @home-assistant/core
|
||||
/homeassistant/components/sensorpush/ @bdraco
|
||||
/tests/components/sensorpush/ @bdraco
|
||||
/homeassistant/components/sentry/ @dcramer @frenck
|
||||
/tests/components/sentry/ @dcramer @frenck
|
||||
/homeassistant/components/senz/ @milanmeu
|
||||
@@ -977,6 +998,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/songpal/ @rytilahti @shenxn
|
||||
/homeassistant/components/sonos/ @cgtobi @jjlawren
|
||||
/tests/components/sonos/ @cgtobi @jjlawren
|
||||
/homeassistant/components/soundtouch/ @kroimon
|
||||
/tests/components/soundtouch/ @kroimon
|
||||
/homeassistant/components/spaceapi/ @fabaff
|
||||
/tests/components/spaceapi/ @fabaff
|
||||
/homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87
|
||||
@@ -1021,11 +1044,11 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/switch/ @home-assistant/core
|
||||
/homeassistant/components/switch_as_x/ @home-assistant/core
|
||||
/tests/components/switch_as_x/ @home-assistant/core
|
||||
/homeassistant/components/switchbot/ @danielhiversen @RenierM26
|
||||
/tests/components/switchbot/ @danielhiversen @RenierM26
|
||||
/homeassistant/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
|
||||
/tests/components/switchbot/ @bdraco @danielhiversen @RenierM26 @murtas @Eloston
|
||||
/homeassistant/components/switcher_kis/ @tomerfi @thecode
|
||||
/tests/components/switcher_kis/ @tomerfi @thecode
|
||||
/homeassistant/components/switchmate/ @danielhiversen
|
||||
/homeassistant/components/switchmate/ @danielhiversen @qiz-li
|
||||
/homeassistant/components/syncthing/ @zhulik
|
||||
/tests/components/syncthing/ @zhulik
|
||||
/homeassistant/components/syncthru/ @nielstron
|
||||
@@ -1203,6 +1226,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/xbox_live/ @MartinHjelmare
|
||||
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79
|
||||
/tests/components/xiaomi_ble/ @Jc2k @Ernst79
|
||||
/homeassistant/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
||||
/tests/components/xiaomi_miio/ @rytilahti @syssi @starkillerOG @bieniu
|
||||
/homeassistant/components/xiaomi_tv/ @simse
|
||||
@@ -1226,8 +1251,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/zeroconf/ @bdraco
|
||||
/homeassistant/components/zerproc/ @emlove
|
||||
/tests/components/zerproc/ @emlove
|
||||
/homeassistant/components/zha/ @dmulcahey @adminiuga
|
||||
/tests/components/zha/ @dmulcahey @adminiuga
|
||||
/homeassistant/components/zha/ @dmulcahey @adminiuga @puddly
|
||||
/tests/components/zha/ @dmulcahey @adminiuga @puddly
|
||||
/homeassistant/components/zodiac/ @JulienTant
|
||||
/tests/components/zodiac/ @JulienTant
|
||||
/homeassistant/components/zone/ @home-assistant/core
|
||||
|
||||
@@ -13,9 +13,12 @@ COPY homeassistant/package_constraints.txt homeassistant/homeassistant/
|
||||
RUN \
|
||||
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||
-r homeassistant/requirements.txt --use-deprecated=legacy-resolver
|
||||
COPY requirements_all.txt homeassistant/
|
||||
COPY requirements_all.txt home_assistant_frontend-* homeassistant/
|
||||
RUN \
|
||||
pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||
if ls homeassistant/home_assistant_frontend*.whl 1> /dev/null 2>&1; then \
|
||||
pip3 install --no-cache-dir --no-index homeassistant/home_assistant_frontend-*.whl; \
|
||||
fi \
|
||||
&& pip3 install --no-cache-dir --no-index --only-binary=:all: --find-links "${WHEELS_LINKS}" \
|
||||
-r homeassistant/requirements_all.txt --use-deprecated=legacy-resolver
|
||||
|
||||
## Setup Home Assistant Core
|
||||
|
||||
10
build.yaml
10
build.yaml
@@ -1,11 +1,11 @@
|
||||
image: homeassistant/{arch}-homeassistant
|
||||
shadow_repository: ghcr.io/home-assistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.06.2
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.06.2
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.06.2
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.06.2
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.06.2
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2022.07.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2022.07.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2022.07.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2022.07.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2022.07.0
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -46,7 +46,7 @@ class AuthStore:
|
||||
self._users: dict[str, models.User] | None = None
|
||||
self._groups: dict[str, models.Group] | None = None
|
||||
self._perm_lookup: PermissionLookup | None = None
|
||||
self._store = Store(
|
||||
self._store = Store[dict[str, list[dict[str, Any]]]](
|
||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||
)
|
||||
self._lock = asyncio.Lock()
|
||||
@@ -483,9 +483,10 @@ class AuthStore:
|
||||
jwt_key=rt_dict["jwt_key"],
|
||||
last_used_at=last_used_at,
|
||||
last_used_ip=rt_dict.get("last_used_ip"),
|
||||
credential=credentials.get(rt_dict.get("credential_id")),
|
||||
version=rt_dict.get("version"),
|
||||
)
|
||||
if "credential_id" in rt_dict:
|
||||
token.credential = credentials.get(rt_dict["credential_id"])
|
||||
users[rt_dict["user_id"]].refresh_tokens[token.id] = token
|
||||
|
||||
self._groups = groups
|
||||
|
||||
@@ -7,7 +7,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import attr
|
||||
import voluptuous as vol
|
||||
@@ -100,7 +100,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._user_settings: _UsersDict | None = None
|
||||
self._user_store = Store(
|
||||
self._user_store = Store[dict[str, dict[str, Any]]](
|
||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||
)
|
||||
self._include = config.get(CONF_INCLUDE, [])
|
||||
@@ -119,10 +119,8 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
if self._user_settings is not None:
|
||||
return
|
||||
|
||||
if (data := await self._user_store.async_load()) is None or not isinstance(
|
||||
data, dict
|
||||
):
|
||||
data = {STORAGE_USERS: {}}
|
||||
if (data := await self._user_store.async_load()) is None:
|
||||
data = cast(dict[str, dict[str, Any]], {STORAGE_USERS: {}})
|
||||
|
||||
self._user_settings = {
|
||||
user_id: NotifySetting(**setting)
|
||||
@@ -322,6 +320,7 @@ class NotifySetupFlow(SetupFlow):
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
hass = self._auth_module.hass
|
||||
assert self._secret and self._count
|
||||
if user_input:
|
||||
verified = await hass.async_add_executor_job(
|
||||
_verify_otp, self._secret, user_input["code"], self._count
|
||||
@@ -336,7 +335,6 @@ class NotifySetupFlow(SetupFlow):
|
||||
errors["base"] = "invalid_code"
|
||||
|
||||
# generate code every time, no retry logic
|
||||
assert self._secret and self._count
|
||||
code = await hass.async_add_executor_job(
|
||||
_generate_otp, self._secret, self._count
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -77,7 +77,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
"""Initialize the user data store."""
|
||||
super().__init__(hass, config)
|
||||
self._users: dict[str, str] | None = None
|
||||
self._user_store = Store(
|
||||
self._user_store = Store[dict[str, dict[str, str]]](
|
||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||
)
|
||||
self._init_lock = asyncio.Lock()
|
||||
@@ -93,16 +93,14 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
if self._users is not None:
|
||||
return
|
||||
|
||||
if (data := await self._user_store.async_load()) is None or not isinstance(
|
||||
data, dict
|
||||
):
|
||||
data = {STORAGE_USERS: {}}
|
||||
if (data := await self._user_store.async_load()) is None:
|
||||
data = cast(dict[str, dict[str, str]], {STORAGE_USERS: {}})
|
||||
|
||||
self._users = data.get(STORAGE_USERS, {})
|
||||
|
||||
async def _async_save(self) -> None:
|
||||
"""Save data."""
|
||||
await self._user_store.async_save({STORAGE_USERS: self._users})
|
||||
await self._user_store.async_save({STORAGE_USERS: self._users or {}})
|
||||
|
||||
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
|
||||
"""Create a ota_secret for user."""
|
||||
|
||||
@@ -61,10 +61,10 @@ class Data:
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the user data store."""
|
||||
self.hass = hass
|
||||
self._store = Store(
|
||||
self._store = Store[dict[str, list[dict[str, str]]]](
|
||||
hass, STORAGE_VERSION, STORAGE_KEY, private=True, atomic_writes=True
|
||||
)
|
||||
self._data: dict[str, Any] | None = None
|
||||
self._data: dict[str, list[dict[str, str]]] | None = None
|
||||
# Legacy mode will allow usernames to start/end with whitespace
|
||||
# and will compare usernames case-insensitive.
|
||||
# Remove in 2020 or when we launch 1.0.
|
||||
@@ -80,10 +80,8 @@ class Data:
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
if (data := await self._store.async_load()) is None or not isinstance(
|
||||
data, dict
|
||||
):
|
||||
data = {"users": []}
|
||||
if (data := await self._store.async_load()) is None:
|
||||
data = cast(dict[str, list[dict[str, str]]], {"users": []})
|
||||
|
||||
seen: set[str] = set()
|
||||
|
||||
@@ -123,7 +121,8 @@ class Data:
|
||||
@property
|
||||
def users(self) -> list[dict[str, str]]:
|
||||
"""Return users."""
|
||||
return self._data["users"] # type: ignore[index,no-any-return]
|
||||
assert self._data is not None
|
||||
return self._data["users"]
|
||||
|
||||
def validate_login(self, username: str, password: str) -> None:
|
||||
"""Validate a username and password.
|
||||
|
||||
@@ -4,15 +4,15 @@ from __future__ import annotations
|
||||
from enum import Enum
|
||||
from typing import Any, TypeVar
|
||||
|
||||
_StrEnumT = TypeVar("_StrEnumT", bound="StrEnum")
|
||||
_StrEnumSelfT = TypeVar("_StrEnumSelfT", bound="StrEnum")
|
||||
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
"""Partial backport of Python 3.11's StrEnum for our basic use cases."""
|
||||
|
||||
def __new__(
|
||||
cls: type[_StrEnumT], value: str, *args: Any, **kwargs: Any
|
||||
) -> _StrEnumT:
|
||||
cls: type[_StrEnumSelfT], value: str, *args: Any, **kwargs: Any
|
||||
) -> _StrEnumSelfT:
|
||||
"""Create a new StrEnum instance."""
|
||||
if not isinstance(value, str):
|
||||
raise TypeError(f"{value!r} is not a string")
|
||||
|
||||
@@ -24,7 +24,7 @@ from .const import (
|
||||
SIGNAL_BOOTSTRAP_INTEGRATONS,
|
||||
)
|
||||
from .exceptions import HomeAssistantError
|
||||
from .helpers import area_registry, device_registry, entity_registry
|
||||
from .helpers import area_registry, device_registry, entity_registry, recorder
|
||||
from .helpers.dispatcher import async_dispatcher_send
|
||||
from .helpers.typing import ConfigType
|
||||
from .setup import (
|
||||
@@ -35,7 +35,6 @@ from .setup import (
|
||||
async_setup_component,
|
||||
)
|
||||
from .util import dt as dt_util
|
||||
from .util.async_ import gather_with_concurrency
|
||||
from .util.logging import async_activate_log_queue_handler
|
||||
from .util.package import async_get_user_site, is_virtual_env
|
||||
|
||||
@@ -67,10 +66,19 @@ LOGGING_INTEGRATIONS = {
|
||||
# Error logging
|
||||
"system_log",
|
||||
"sentry",
|
||||
}
|
||||
FRONTEND_INTEGRATIONS = {
|
||||
# Get the frontend up and running as soon as possible so problem
|
||||
# integrations can be removed and database migration status is
|
||||
# visible in frontend
|
||||
"frontend",
|
||||
}
|
||||
RECORDER_INTEGRATIONS = {
|
||||
# Setup after frontend
|
||||
# To record data
|
||||
"recorder",
|
||||
}
|
||||
DISCOVERY_INTEGRATIONS = ("dhcp", "ssdp", "usb", "zeroconf")
|
||||
DISCOVERY_INTEGRATIONS = ("bluetooth", "dhcp", "ssdp", "usb", "zeroconf")
|
||||
STAGE_1_INTEGRATIONS = {
|
||||
# We need to make sure discovery integrations
|
||||
# update their deps before stage 2 integrations
|
||||
@@ -84,10 +92,6 @@ STAGE_1_INTEGRATIONS = {
|
||||
"cloud",
|
||||
# Ensure supervisor is available
|
||||
"hassio",
|
||||
# Get the frontend up and running as soon
|
||||
# as possible so problem integrations can
|
||||
# be removed
|
||||
"frontend",
|
||||
}
|
||||
|
||||
|
||||
@@ -285,7 +289,9 @@ def async_enable_logging(
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
fmt = "%(asctime)s %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||
fmt = (
|
||||
"%(asctime)s.%(msecs)03d %(levelname)s (%(threadName)s) [%(name)s] %(message)s"
|
||||
)
|
||||
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
if not log_no_color:
|
||||
@@ -477,14 +483,9 @@ async def _async_set_up_integrations(
|
||||
|
||||
integrations_to_process = [
|
||||
int_or_exc
|
||||
for int_or_exc in await gather_with_concurrency(
|
||||
loader.MAX_LOAD_CONCURRENTLY,
|
||||
*(
|
||||
loader.async_get_integration(hass, domain)
|
||||
for domain in old_to_resolve
|
||||
),
|
||||
return_exceptions=True,
|
||||
)
|
||||
for int_or_exc in (
|
||||
await loader.async_get_integrations(hass, old_to_resolve)
|
||||
).values()
|
||||
if isinstance(int_or_exc, loader.Integration)
|
||||
]
|
||||
resolve_dependencies_tasks = [
|
||||
@@ -508,11 +509,43 @@ async def _async_set_up_integrations(
|
||||
|
||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||
|
||||
def _cache_uname_processor() -> None:
|
||||
"""Cache the result of platform.uname().processor in the executor.
|
||||
|
||||
Multiple modules call this function at startup which
|
||||
executes a blocking subprocess call. This is a problem for the
|
||||
asyncio event loop. By primeing the cache of uname we can
|
||||
avoid the blocking call in the event loop.
|
||||
"""
|
||||
platform.uname().processor # pylint: disable=expression-not-assigned
|
||||
|
||||
# Load the registries and cache the result of platform.uname().processor
|
||||
await asyncio.gather(
|
||||
device_registry.async_load(hass),
|
||||
entity_registry.async_load(hass),
|
||||
area_registry.async_load(hass),
|
||||
hass.async_add_executor_job(_cache_uname_processor),
|
||||
)
|
||||
|
||||
# Initialize recorder
|
||||
if "recorder" in domains_to_setup:
|
||||
recorder.async_initialize_recorder(hass)
|
||||
|
||||
# Load logging as soon as possible
|
||||
if logging_domains := domains_to_setup & LOGGING_INTEGRATIONS:
|
||||
_LOGGER.info("Setting up logging: %s", logging_domains)
|
||||
await async_setup_multi_components(hass, logging_domains, config)
|
||||
|
||||
# Setup frontend
|
||||
if frontend_domains := domains_to_setup & FRONTEND_INTEGRATIONS:
|
||||
_LOGGER.info("Setting up frontend: %s", frontend_domains)
|
||||
await async_setup_multi_components(hass, frontend_domains, config)
|
||||
|
||||
# Setup recorder
|
||||
if recorder_domains := domains_to_setup & RECORDER_INTEGRATIONS:
|
||||
_LOGGER.info("Setting up recorder: %s", recorder_domains)
|
||||
await async_setup_multi_components(hass, recorder_domains, config)
|
||||
|
||||
# Start up debuggers. Start these first in case they want to wait.
|
||||
if debuggers := domains_to_setup & DEBUGGER_INTEGRATIONS:
|
||||
_LOGGER.debug("Setting up debuggers: %s", debuggers)
|
||||
@@ -522,7 +555,8 @@ async def _async_set_up_integrations(
|
||||
stage_1_domains: set[str] = set()
|
||||
|
||||
# Find all dependencies of any dependency of any stage 1 integration that
|
||||
# we plan on loading and promote them to stage 1
|
||||
# we plan on loading and promote them to stage 1. This is done only to not
|
||||
# get misleading log messages
|
||||
deps_promotion: set[str] = STAGE_1_INTEGRATIONS
|
||||
while deps_promotion:
|
||||
old_deps_promotion = deps_promotion
|
||||
@@ -539,24 +573,13 @@ async def _async_set_up_integrations(
|
||||
|
||||
deps_promotion.update(dep_itg.all_dependencies)
|
||||
|
||||
stage_2_domains = domains_to_setup - logging_domains - debuggers - stage_1_domains
|
||||
|
||||
def _cache_uname_processor() -> None:
|
||||
"""Cache the result of platform.uname().processor in the executor.
|
||||
|
||||
Multiple modules call this function at startup which
|
||||
executes a blocking subprocess call. This is a problem for the
|
||||
asyncio event loop. By primeing the cache of uname we can
|
||||
avoid the blocking call in the event loop.
|
||||
"""
|
||||
platform.uname().processor # pylint: disable=expression-not-assigned
|
||||
|
||||
# Load the registries
|
||||
await asyncio.gather(
|
||||
device_registry.async_load(hass),
|
||||
entity_registry.async_load(hass),
|
||||
area_registry.async_load(hass),
|
||||
hass.async_add_executor_job(_cache_uname_processor),
|
||||
stage_2_domains = (
|
||||
domains_to_setup
|
||||
- logging_domains
|
||||
- frontend_domains
|
||||
- recorder_domains
|
||||
- debuggers
|
||||
- stage_1_domains
|
||||
)
|
||||
|
||||
# Start setup
|
||||
|
||||
@@ -103,7 +103,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
hass.data[DOMAIN] = AbodeSystem(abode, polling)
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
await setup_hass_events(hass)
|
||||
await hass.async_add_executor_job(setup_hass_services, hass)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "\u518d\u8a8d\u8a3c\u306b\u6210\u529f\u3057\u307e\u3057\u305f",
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "Palavra-passe",
|
||||
"username": "Endere\u00e7o de e-mail"
|
||||
"username": "Email"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ from aiohttp.client_exceptions import ClientConnectorError
|
||||
from async_timeout import timeout
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN
|
||||
from .const import ATTR_FORECAST, CONF_FORECAST, DOMAIN, MANUFACTURER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,6 +28,7 @@ PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up AccuWeather as config entry."""
|
||||
api_key: str = entry.data[CONF_API_KEY]
|
||||
name: str = entry.data[CONF_NAME]
|
||||
assert entry.unique_id is not None
|
||||
location_key = entry.unique_id
|
||||
forecast: bool = entry.options.get(CONF_FORECAST, False)
|
||||
@@ -35,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
websession = async_get_clientsession(hass)
|
||||
|
||||
coordinator = AccuWeatherDataUpdateCoordinator(
|
||||
hass, websession, api_key, location_key, forecast
|
||||
hass, websession, api_key, location_key, forecast, name
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -43,7 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
@@ -73,12 +76,27 @@ class AccuWeatherDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
api_key: str,
|
||||
location_key: str,
|
||||
forecast: bool,
|
||||
name: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self.location_key = location_key
|
||||
self.forecast = forecast
|
||||
self.is_metric = hass.config.units.is_metric
|
||||
self.accuweather = AccuWeather(api_key, session, location_key=self.location_key)
|
||||
self.accuweather = AccuWeather(api_key, session, location_key=location_key)
|
||||
self.device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, location_key)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=name,
|
||||
# You don't need to provide specific details for the URL,
|
||||
# so passing in _ characters is fine if the location key
|
||||
# is correct
|
||||
configuration_url=(
|
||||
"http://accuweather.com/en/"
|
||||
f"_/_/{location_key}/"
|
||||
f"weather-forecast/{location_key}/"
|
||||
),
|
||||
)
|
||||
|
||||
# Enabling the forecast download increases the number of requests per data
|
||||
# update, we use 40 minutes for current condition only and 80 minutes for
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_CLOUDY,
|
||||
@@ -20,22 +19,6 @@ from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_SUNNY,
|
||||
ATTR_CONDITION_WINDY,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
LENGTH_FEET,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_METERS,
|
||||
LENGTH_MILLIMETERS,
|
||||
PERCENTAGE,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
TIME_HOURS,
|
||||
UV_INDEX,
|
||||
)
|
||||
|
||||
from .model import AccuWeatherSensorDescription
|
||||
|
||||
API_IMPERIAL: Final = "Imperial"
|
||||
API_METRIC: Final = "Metric"
|
||||
@@ -45,7 +28,6 @@ CONF_FORECAST: Final = "forecast"
|
||||
DOMAIN: Final = "accuweather"
|
||||
MANUFACTURER: Final = "AccuWeather, Inc."
|
||||
MAX_FORECAST_DAYS: Final = 4
|
||||
NAME: Final = "AccuWeather"
|
||||
|
||||
CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_CLEAR_NIGHT: [33, 34, 37],
|
||||
@@ -63,264 +45,3 @@ CONDITION_CLASSES: Final[dict[str, list[int]]] = {
|
||||
ATTR_CONDITION_SUNNY: [1, 2, 5],
|
||||
ATTR_CONDITION_WINDY: [32],
|
||||
}
|
||||
|
||||
FORECAST_SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverDay",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover Day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverNight",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover Night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Grass",
|
||||
icon="mdi:grass",
|
||||
name="Grass Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="HoursOfSun",
|
||||
icon="mdi:weather-partly-cloudy",
|
||||
name="Hours Of Sun",
|
||||
unit_metric=TIME_HOURS,
|
||||
unit_imperial=TIME_HOURS,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Mold",
|
||||
icon="mdi:blur",
|
||||
name="Mold Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ozone",
|
||||
icon="mdi:vector-triangle",
|
||||
name="Ozone",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ragweed",
|
||||
icon="mdi:sprout",
|
||||
name="Ragweed Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade Min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityDay",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm Probability Day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityNight",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm Probability Night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Tree",
|
||||
icon="mdi:tree-outline",
|
||||
name="Tree Pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV Index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust Day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust Night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES: Final[tuple[AccuWeatherSensorDescription, ...]] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="ApparentTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Apparent Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ceiling",
|
||||
icon="mdi:weather-fog",
|
||||
name="Cloud Ceiling",
|
||||
unit_metric=LENGTH_METERS,
|
||||
unit_imperial=LENGTH_FEET,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCover",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud Cover",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="DewPoint",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Dew Point",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShade",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel Temperature Shade",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Precipitation",
|
||||
icon="mdi:weather-rainy",
|
||||
name="Precipitation",
|
||||
unit_metric=LENGTH_MILLIMETERS,
|
||||
unit_imperial=LENGTH_INCHES,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="PressureTendency",
|
||||
device_class="accuweather__pressure_tendency",
|
||||
icon="mdi:gauge",
|
||||
name="Pressure Tendency",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV Index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WetBulbTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wet Bulb Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindChillTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wind Chill Temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Wind",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGust",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind Gust",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
"""Type definitions for AccuWeather integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.sensor import SensorEntityDescription
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||
"""Class describing AccuWeather sensor entities."""
|
||||
|
||||
unit_metric: str | None = None
|
||||
unit_imperial: str | None = None
|
||||
@@ -1,14 +1,31 @@
|
||||
"""Support for the AccuWeather service."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
LENGTH_FEET,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_METERS,
|
||||
LENGTH_MILLIMETERS,
|
||||
PERCENTAGE,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
TIME_HOURS,
|
||||
UV_INDEX,
|
||||
)
|
||||
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
|
||||
@@ -20,28 +37,292 @@ from .const import (
|
||||
ATTR_FORECAST,
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
FORECAST_SENSOR_TYPES,
|
||||
MANUFACTURER,
|
||||
MAX_FORECAST_DAYS,
|
||||
NAME,
|
||||
SENSOR_TYPES,
|
||||
)
|
||||
from .model import AccuWeatherSensorDescription
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||
"""Class describing AccuWeather sensor entities."""
|
||||
|
||||
unit_metric: str | None = None
|
||||
unit_imperial: str | None = None
|
||||
|
||||
|
||||
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverDay",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCoverNight",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Grass",
|
||||
icon="mdi:grass",
|
||||
name="Grass pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="HoursOfSun",
|
||||
icon="mdi:weather-partly-cloudy",
|
||||
name="Hours of sun",
|
||||
unit_metric=TIME_HOURS,
|
||||
unit_imperial=TIME_HOURS,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Mold",
|
||||
icon="mdi:blur",
|
||||
name="Mold pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ozone",
|
||||
icon="mdi:vector-triangle",
|
||||
name="Ozone",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ragweed",
|
||||
icon="mdi:sprout",
|
||||
name="Ragweed pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMax",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade max",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShadeMin",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade min",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityDay",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm probability day",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="ThunderstormProbabilityNight",
|
||||
icon="mdi:weather-lightning",
|
||||
name="Thunderstorm probability night",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Tree",
|
||||
icon="mdi:tree-outline",
|
||||
name="Tree pollen",
|
||||
unit_metric=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
unit_imperial=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind gust day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGustNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind gust night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindDay",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind day",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindNight",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind night",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="ApparentTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Apparent temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Ceiling",
|
||||
icon="mdi:weather-fog",
|
||||
name="Cloud ceiling",
|
||||
unit_metric=LENGTH_METERS,
|
||||
unit_imperial=LENGTH_FEET,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="CloudCover",
|
||||
icon="mdi:weather-cloudy",
|
||||
name="Cloud cover",
|
||||
unit_metric=PERCENTAGE,
|
||||
unit_imperial=PERCENTAGE,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="DewPoint",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Dew point",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="RealFeelTemperatureShade",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="RealFeel temperature shade",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Precipitation",
|
||||
icon="mdi:weather-rainy",
|
||||
name="Precipitation",
|
||||
unit_metric=LENGTH_MILLIMETERS,
|
||||
unit_imperial=LENGTH_INCHES,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="PressureTendency",
|
||||
device_class="accuweather__pressure_tendency",
|
||||
icon="mdi:gauge",
|
||||
name="Pressure tendency",
|
||||
unit_metric=None,
|
||||
unit_imperial=None,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
icon="mdi:weather-sunny",
|
||||
name="UV index",
|
||||
unit_metric=UV_INDEX,
|
||||
unit_imperial=UV_INDEX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WetBulbTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wet bulb temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindChillTemperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
name="Wind chill temperature",
|
||||
unit_metric=TEMP_CELSIUS,
|
||||
unit_imperial=TEMP_FAHRENHEIT,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="Wind",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="WindGust",
|
||||
icon="mdi:weather-windy",
|
||||
name="Wind gust",
|
||||
unit_metric=SPEED_KILOMETERS_PER_HOUR,
|
||||
unit_imperial=SPEED_MILES_PER_HOUR,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Add AccuWeather entities from a config_entry."""
|
||||
name: str = entry.data[CONF_NAME]
|
||||
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
sensors: list[AccuWeatherSensor] = []
|
||||
for description in SENSOR_TYPES:
|
||||
sensors.append(AccuWeatherSensor(name, coordinator, description))
|
||||
sensors.append(AccuWeatherSensor(coordinator, description))
|
||||
|
||||
if coordinator.forecast:
|
||||
for description in FORECAST_SENSOR_TYPES:
|
||||
@@ -50,9 +331,7 @@ async def async_setup_entry(
|
||||
# locations.
|
||||
if description.key in coordinator.data[ATTR_FORECAST][0]:
|
||||
sensors.append(
|
||||
AccuWeatherSensor(
|
||||
name, coordinator, description, forecast_day=day
|
||||
)
|
||||
AccuWeatherSensor(coordinator, description, forecast_day=day)
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
@@ -64,11 +343,11 @@ class AccuWeatherSensor(
|
||||
"""Define an AccuWeather entity."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
entity_description: AccuWeatherSensorDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
coordinator: AccuWeatherDataUpdateCoordinator,
|
||||
description: AccuWeatherSensorDescription,
|
||||
forecast_day: int | None = None,
|
||||
@@ -81,12 +360,11 @@ class AccuWeatherSensor(
|
||||
)
|
||||
self._attrs: dict[str, Any] = {}
|
||||
if forecast_day is not None:
|
||||
self._attr_name = f"{name} {description.name} {forecast_day}d"
|
||||
self._attr_name = f"{description.name} {forecast_day}d"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}-{forecast_day}".lower()
|
||||
)
|
||||
else:
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.location_key}-{description.key}".lower()
|
||||
)
|
||||
@@ -96,12 +374,7 @@ class AccuWeatherSensor(
|
||||
else:
|
||||
self._unit_system = API_IMPERIAL
|
||||
self._attr_native_unit_of_measurement = description.unit_imperial
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, coordinator.location_key)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=NAME,
|
||||
)
|
||||
self._attr_device_info = coordinator.device_info
|
||||
self.forecast_day = forecast_day
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002"
|
||||
"default": "\u4e00\u90e8\u306e\u30bb\u30f3\u30b5\u30fc\u306f\u3001\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u69cb\u6210\u5f8c\u3001\u30a8\u30f3\u30c6\u30a3\u30c6\u30a3\u30ec\u30b8\u30b9\u30c8\u30ea\u3067\u305d\u308c\u3089\u3092\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002\n\u5929\u6c17\u4e88\u5831\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u306f\u6709\u52b9\u306b\u306a\u3063\u3066\u3044\u307e\u305b\u3093\u3002\u7d71\u5408\u306e\u30aa\u30d7\u30b7\u30e7\u30f3\u3067\u6709\u52b9\u306b\u3067\u304d\u307e\u3059\u3002"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "Dost\u0119p do serwera AccuWeather",
|
||||
"can_reach_server": "Dost\u0119p do serwera",
|
||||
"remaining_requests": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"abort": {
|
||||
"single_instance_allowed": "J\u00e1 configurado. Apenas uma \u00fanica configura\u00e7\u00e3o \u00e9 poss\u00edvel."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Alguns sensores n\u00e3o s\u00e3o ativados por defeito. Podem ser ativados no registo da entidade ap\u00f3s a configura\u00e7\u00e3o da integra\u00e7\u00e3o.\nA previs\u00e3o do tempo n\u00e3o est\u00e1 ativada por defeito. Pode ativ\u00e1-la nas op\u00e7\u00f5es de integra\u00e7\u00e3o."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Falha na liga\u00e7\u00e3o",
|
||||
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||
|
||||
@@ -6,26 +6,30 @@ from typing import Any, cast
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
Forecast,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_NAME,
|
||||
LENGTH_INCHES,
|
||||
LENGTH_KILOMETERS,
|
||||
LENGTH_MILES,
|
||||
LENGTH_MILLIMETERS,
|
||||
PRESSURE_HPA,
|
||||
PRESSURE_INHG,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
SPEED_MILES_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
TEMP_FAHRENHEIT,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
@@ -38,8 +42,6 @@ from .const import (
|
||||
ATTRIBUTION,
|
||||
CONDITION_CLASSES,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
NAME,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
@@ -49,11 +51,10 @@ async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Add a AccuWeather weather entity from a config_entry."""
|
||||
name: str = entry.data[CONF_NAME]
|
||||
|
||||
coordinator: AccuWeatherDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities([AccuWeatherEntity(name, coordinator)])
|
||||
async_add_entities([AccuWeatherEntity(coordinator)])
|
||||
|
||||
|
||||
class AccuWeatherEntity(
|
||||
@@ -61,37 +62,31 @@ class AccuWeatherEntity(
|
||||
):
|
||||
"""Define an AccuWeather entity."""
|
||||
|
||||
def __init__(
|
||||
self, name: str, coordinator: AccuWeatherDataUpdateCoordinator
|
||||
) -> None:
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: AccuWeatherDataUpdateCoordinator) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator)
|
||||
self._unit_system = API_METRIC if coordinator.is_metric else API_IMPERIAL
|
||||
wind_speed_unit = self.coordinator.data["Wind"]["Speed"][self._unit_system][
|
||||
"Unit"
|
||||
]
|
||||
if wind_speed_unit == "mi/h":
|
||||
self._attr_wind_speed_unit = SPEED_MILES_PER_HOUR
|
||||
# Coordinator data is used also for sensors which don't have units automatically
|
||||
# converted, hence the weather entity's native units follow the configured unit
|
||||
# system
|
||||
if coordinator.is_metric:
|
||||
self._attr_native_precipitation_unit = LENGTH_MILLIMETERS
|
||||
self._attr_native_pressure_unit = PRESSURE_HPA
|
||||
self._attr_native_temperature_unit = TEMP_CELSIUS
|
||||
self._attr_native_visibility_unit = LENGTH_KILOMETERS
|
||||
self._attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
|
||||
self._unit_system = API_METRIC
|
||||
else:
|
||||
self._attr_wind_speed_unit = wind_speed_unit
|
||||
self._attr_name = name
|
||||
self._unit_system = API_IMPERIAL
|
||||
self._attr_native_precipitation_unit = LENGTH_INCHES
|
||||
self._attr_native_pressure_unit = PRESSURE_INHG
|
||||
self._attr_native_temperature_unit = TEMP_FAHRENHEIT
|
||||
self._attr_native_visibility_unit = LENGTH_MILES
|
||||
self._attr_native_wind_speed_unit = SPEED_MILES_PER_HOUR
|
||||
self._attr_unique_id = coordinator.location_key
|
||||
self._attr_temperature_unit = (
|
||||
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
|
||||
)
|
||||
self._attr_attribution = ATTRIBUTION
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, coordinator.location_key)},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=NAME,
|
||||
# You don't need to provide specific details for the URL,
|
||||
# so passing in _ characters is fine if the location key
|
||||
# is correct
|
||||
configuration_url="http://accuweather.com/en/"
|
||||
f"_/_/{coordinator.location_key}/"
|
||||
f"weather-forecast/{coordinator.location_key}/",
|
||||
)
|
||||
self._attr_device_info = coordinator.device_info
|
||||
|
||||
@property
|
||||
def condition(self) -> str | None:
|
||||
@@ -106,14 +101,14 @@ class AccuWeatherEntity(
|
||||
return None
|
||||
|
||||
@property
|
||||
def temperature(self) -> float:
|
||||
def native_temperature(self) -> float:
|
||||
"""Return the temperature."""
|
||||
return cast(
|
||||
float, self.coordinator.data["Temperature"][self._unit_system]["Value"]
|
||||
)
|
||||
|
||||
@property
|
||||
def pressure(self) -> float:
|
||||
def native_pressure(self) -> float:
|
||||
"""Return the pressure."""
|
||||
return cast(
|
||||
float, self.coordinator.data["Pressure"][self._unit_system]["Value"]
|
||||
@@ -125,7 +120,7 @@ class AccuWeatherEntity(
|
||||
return cast(int, self.coordinator.data["RelativeHumidity"])
|
||||
|
||||
@property
|
||||
def wind_speed(self) -> float:
|
||||
def native_wind_speed(self) -> float:
|
||||
"""Return the wind speed."""
|
||||
return cast(
|
||||
float, self.coordinator.data["Wind"]["Speed"][self._unit_system]["Value"]
|
||||
@@ -137,7 +132,7 @@ class AccuWeatherEntity(
|
||||
return cast(int, self.coordinator.data["Wind"]["Direction"]["Degrees"])
|
||||
|
||||
@property
|
||||
def visibility(self) -> float:
|
||||
def native_visibility(self) -> float:
|
||||
"""Return the visibility."""
|
||||
return cast(
|
||||
float, self.coordinator.data["Visibility"][self._unit_system]["Value"]
|
||||
@@ -162,9 +157,9 @@ class AccuWeatherEntity(
|
||||
return [
|
||||
{
|
||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||
ATTR_FORECAST_TEMP: item["TemperatureMax"]["Value"],
|
||||
ATTR_FORECAST_TEMP_LOW: item["TemperatureMin"]["Value"],
|
||||
ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(item),
|
||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"]["Value"],
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"]["Value"],
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION: self._calc_precipitation(item),
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: round(
|
||||
mean(
|
||||
[
|
||||
@@ -173,7 +168,7 @@ class AccuWeatherEntity(
|
||||
]
|
||||
)
|
||||
),
|
||||
ATTR_FORECAST_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED: item["WindDay"]["Speed"]["Value"],
|
||||
ATTR_FORECAST_WIND_BEARING: item["WindDay"]["Direction"]["Degrees"],
|
||||
ATTR_FORECAST_CONDITION: [
|
||||
k for k, v in CONDITION_CLASSES.items() if item["IconDay"] in v
|
||||
|
||||
@@ -19,7 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
return False
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
|
||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ PLATFORMS = [Platform.CLIMATE]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Adax from a config entry."""
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
17
homeassistant/components/adax/translations/pt.json
Normal file
17
homeassistant/components/adax/translations/pt.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||
},
|
||||
"step": {
|
||||
"cloud": {
|
||||
"data": {
|
||||
"password": "Palavra-passe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
"""Support for AdGuard Home."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
@@ -22,13 +20,10 @@ from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import (
|
||||
CONF_FORCE,
|
||||
DATA_ADGUARD_CLIENT,
|
||||
DATA_ADGUARD_VERSION,
|
||||
DOMAIN,
|
||||
SERVICE_ADD_URL,
|
||||
SERVICE_DISABLE_URL,
|
||||
@@ -37,8 +32,6 @@ from .const import (
|
||||
SERVICE_REMOVE_URL,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_URL_SCHEMA = vol.Schema({vol.Required(CONF_URL): cv.url})
|
||||
SERVICE_ADD_URL_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_URL): cv.url}
|
||||
@@ -70,7 +63,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
except AdGuardHomeConnectionError as exception:
|
||||
raise ConfigEntryNotReady from exception
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async def add_url(call: ServiceCall) -> None:
|
||||
"""Service call to add a new filter subscription to AdGuard Home."""
|
||||
@@ -115,102 +108,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload AdGuard Home config entry."""
|
||||
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_ENABLE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if not hass.data[DOMAIN]:
|
||||
hass.services.async_remove(DOMAIN, SERVICE_ADD_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_REMOVE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_ENABLE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_DISABLE_URL)
|
||||
hass.services.async_remove(DOMAIN, SERVICE_REFRESH)
|
||||
del hass.data[DOMAIN]
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class AdGuardHomeEntity(Entity):
|
||||
"""Defines a base AdGuard Home entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
adguard: AdGuardHome,
|
||||
entry: ConfigEntry,
|
||||
name: str,
|
||||
icon: str,
|
||||
enabled_default: bool = True,
|
||||
) -> None:
|
||||
"""Initialize the AdGuard Home entity."""
|
||||
self._available = True
|
||||
self._enabled_default = enabled_default
|
||||
self._icon = icon
|
||||
self._name = name
|
||||
self._entry = entry
|
||||
self.adguard = adguard
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
"""Return the mdi icon of the entity."""
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
"""Return if the entity should be enabled when first added to the entity registry."""
|
||||
return self._enabled_default
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self._available
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._adguard_update()
|
||||
self._available = True
|
||||
except AdGuardHomeError:
|
||||
if self._available:
|
||||
_LOGGER.debug(
|
||||
"An error occurred while updating AdGuard Home sensor",
|
||||
exc_info=True,
|
||||
)
|
||||
self._available = False
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
|
||||
"""Defines a AdGuard Home device entity."""
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device information about this AdGuard Home instance."""
|
||||
if self._entry.source == SOURCE_HASSIO:
|
||||
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
|
||||
else:
|
||||
if self.adguard.tls:
|
||||
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
|
||||
else:
|
||||
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
|
||||
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={
|
||||
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
|
||||
},
|
||||
manufacturer="AdGuard Team",
|
||||
name="AdGuard Home",
|
||||
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
|
||||
DATA_ADGUARD_VERSION
|
||||
),
|
||||
configuration_url=config_url,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Constants for the AdGuard Home integration."""
|
||||
import logging
|
||||
|
||||
DOMAIN = "adguard"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
DATA_ADGUARD_CLIENT = "adguard_client"
|
||||
DATA_ADGUARD_VERSION = "adguard_version"
|
||||
|
||||
|
||||
69
homeassistant/components/adguard/entity.py
Normal file
69
homeassistant/components/adguard/entity.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""AdGuard Home base entity."""
|
||||
from __future__ import annotations
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeError
|
||||
|
||||
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo, Entity
|
||||
|
||||
from .const import DATA_ADGUARD_VERSION, DOMAIN, LOGGER
|
||||
|
||||
|
||||
class AdGuardHomeEntity(Entity):
|
||||
"""Defines a base AdGuard Home entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_available = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
adguard: AdGuardHome,
|
||||
entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize the AdGuard Home entity."""
|
||||
self._entry = entry
|
||||
self.adguard = adguard
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
if not self.enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._adguard_update()
|
||||
self._attr_available = True
|
||||
except AdGuardHomeError:
|
||||
if self._attr_available:
|
||||
LOGGER.debug(
|
||||
"An error occurred while updating AdGuard Home sensor",
|
||||
exc_info=True,
|
||||
)
|
||||
self._attr_available = False
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device information about this AdGuard Home instance."""
|
||||
if self._entry.source == SOURCE_HASSIO:
|
||||
config_url = "homeassistant://hassio/ingress/a0d7b954_adguard"
|
||||
elif self.adguard.tls:
|
||||
config_url = f"https://{self.adguard.host}:{self.adguard.port}"
|
||||
else:
|
||||
config_url = f"http://{self.adguard.host}:{self.adguard.port}"
|
||||
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={
|
||||
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore[arg-type]
|
||||
},
|
||||
manufacturer="AdGuard Team",
|
||||
name="AdGuard Home",
|
||||
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
|
||||
DATA_ADGUARD_VERSION
|
||||
),
|
||||
configuration_url=config_url,
|
||||
)
|
||||
@@ -1,24 +1,102 @@
|
||||
"""Support for AdGuard Home sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, TIME_MILLISECONDS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AdGuardHomeDeviceEntity
|
||||
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
|
||||
from .entity import AdGuardHomeEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=300)
|
||||
PARALLEL_UPDATES = 4
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdGuardHomeEntityDescriptionMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[AdGuardHome], Coroutine[Any, Any, int | float]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdGuardHomeEntityDescription(
|
||||
SensorEntityDescription, AdGuardHomeEntityDescriptionMixin
|
||||
):
|
||||
"""Describes AdGuard Home sensor entity."""
|
||||
|
||||
|
||||
SENSORS: tuple[AdGuardHomeEntityDescription, ...] = (
|
||||
AdGuardHomeEntityDescription(
|
||||
key="dns_queries",
|
||||
name="DNS queries",
|
||||
icon="mdi:magnify",
|
||||
native_unit_of_measurement="queries",
|
||||
value_fn=lambda adguard: adguard.stats.dns_queries(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_filtering",
|
||||
name="DNS queries blocked",
|
||||
icon="mdi:magnify-close",
|
||||
native_unit_of_measurement="queries",
|
||||
value_fn=lambda adguard: adguard.stats.blocked_filtering(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_percentage",
|
||||
name="DNS queries blocked ratio",
|
||||
icon="mdi:magnify-close",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda adguard: adguard.stats.blocked_percentage(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_parental",
|
||||
name="Parental control blocked",
|
||||
icon="mdi:human-male-girl",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_parental(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="blocked_safebrowsing",
|
||||
name="Safe browsing blocked",
|
||||
icon="mdi:shield-half-full",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_safebrowsing(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="enforced_safesearch",
|
||||
name="Safe searches enforced",
|
||||
icon="mdi:shield-search",
|
||||
native_unit_of_measurement="requests",
|
||||
value_fn=lambda adguard: adguard.stats.replaced_safesearch(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="average_speed",
|
||||
name="Average processing speed",
|
||||
icon="mdi:speedometer",
|
||||
native_unit_of_measurement=TIME_MILLISECONDS,
|
||||
value_fn=lambda adguard: adguard.stats.avg_processing_time(),
|
||||
),
|
||||
AdGuardHomeEntityDescription(
|
||||
key="rules_count",
|
||||
name="Rules count",
|
||||
icon="mdi:counter",
|
||||
native_unit_of_measurement="rules",
|
||||
value_fn=lambda adguard: adguard.filtering.rules_count(allowlist=False),
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
@@ -34,215 +112,39 @@ async def async_setup_entry(
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
||||
|
||||
sensors = [
|
||||
AdGuardHomeDNSQueriesSensor(adguard, entry),
|
||||
AdGuardHomeBlockedFilteringSensor(adguard, entry),
|
||||
AdGuardHomePercentageBlockedSensor(adguard, entry),
|
||||
AdGuardHomeReplacedParentalSensor(adguard, entry),
|
||||
AdGuardHomeReplacedSafeBrowsingSensor(adguard, entry),
|
||||
AdGuardHomeReplacedSafeSearchSensor(adguard, entry),
|
||||
AdGuardHomeAverageProcessingTimeSensor(adguard, entry),
|
||||
AdGuardHomeRulesCountSensor(adguard, entry),
|
||||
]
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
async_add_entities(
|
||||
[AdGuardHomeSensor(adguard, entry, description) for description in SENSORS],
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
|
||||
class AdGuardHomeSensor(AdGuardHomeEntity, SensorEntity):
|
||||
"""Defines a AdGuard Home sensor."""
|
||||
|
||||
entity_description: AdGuardHomeEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
adguard: AdGuardHome,
|
||||
entry: ConfigEntry,
|
||||
name: str,
|
||||
icon: str,
|
||||
measurement: str,
|
||||
unit_of_measurement: str,
|
||||
enabled_default: bool = True,
|
||||
description: AdGuardHomeEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
self._state: int | str | None = None
|
||||
self._unit_of_measurement = unit_of_measurement
|
||||
self.measurement = measurement
|
||||
|
||||
super().__init__(adguard, entry, name, icon, enabled_default)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID for this sensor."""
|
||||
return "_".join(
|
||||
super().__init__(adguard, entry)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = "_".join(
|
||||
[
|
||||
DOMAIN,
|
||||
self.adguard.host,
|
||||
str(self.adguard.port),
|
||||
adguard.host,
|
||||
str(adguard.port),
|
||||
"sensor",
|
||||
self.measurement,
|
||||
description.key,
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> int | str | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit this state is expressed in."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
|
||||
class AdGuardHomeDNSQueriesSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home DNS Queries sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard DNS Queries",
|
||||
"mdi:magnify",
|
||||
"dns_queries",
|
||||
"queries",
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.stats.dns_queries()
|
||||
|
||||
|
||||
class AdGuardHomeBlockedFilteringSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home blocked by filtering sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard DNS Queries Blocked",
|
||||
"mdi:magnify-close",
|
||||
"blocked_filtering",
|
||||
"queries",
|
||||
enabled_default=False,
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.stats.blocked_filtering()
|
||||
|
||||
|
||||
class AdGuardHomePercentageBlockedSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home blocked percentage sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard DNS Queries Blocked Ratio",
|
||||
"mdi:magnify-close",
|
||||
"blocked_percentage",
|
||||
PERCENTAGE,
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
percentage = await self.adguard.stats.blocked_percentage()
|
||||
self._state = f"{percentage:.2f}"
|
||||
|
||||
|
||||
class AdGuardHomeReplacedParentalSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home replaced by parental control sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Parental Control Blocked",
|
||||
"mdi:human-male-girl",
|
||||
"blocked_parental",
|
||||
"requests",
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.stats.replaced_parental()
|
||||
|
||||
|
||||
class AdGuardHomeReplacedSafeBrowsingSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home replaced by safe browsing sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Safe Browsing Blocked",
|
||||
"mdi:shield-half-full",
|
||||
"blocked_safebrowsing",
|
||||
"requests",
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.stats.replaced_safebrowsing()
|
||||
|
||||
|
||||
class AdGuardHomeReplacedSafeSearchSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home replaced by safe search sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Safe Searches Enforced",
|
||||
"mdi:shield-search",
|
||||
"enforced_safesearch",
|
||||
"requests",
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.stats.replaced_safesearch()
|
||||
|
||||
|
||||
class AdGuardHomeAverageProcessingTimeSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home average processing time sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Average Processing Speed",
|
||||
"mdi:speedometer",
|
||||
"average_speed",
|
||||
TIME_MILLISECONDS,
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
average = await self.adguard.stats.avg_processing_time()
|
||||
self._state = f"{average:.2f}"
|
||||
|
||||
|
||||
class AdGuardHomeRulesCountSensor(AdGuardHomeSensor):
|
||||
"""Defines a AdGuard Home rules count sensor."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home sensor."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Rules Count",
|
||||
"mdi:counter",
|
||||
"rules_count",
|
||||
"rules",
|
||||
enabled_default=False,
|
||||
)
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.filtering.rules_count(allowlist=False)
|
||||
value = await self.entity_description.value_fn(self.adguard)
|
||||
self._attr_native_value = value
|
||||
if isinstance(value, float):
|
||||
self._attr_native_value = f"{value:.2f}"
|
||||
|
||||
@@ -1,27 +1,94 @@
|
||||
"""Support for AdGuard Home switches."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError, AdGuardHomeError
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AdGuardHomeDeviceEntity
|
||||
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
from .const import DATA_ADGUARD_CLIENT, DATA_ADGUARD_VERSION, DOMAIN, LOGGER
|
||||
from .entity import AdGuardHomeEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdGuardHomeSwitchEntityDescriptionMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
is_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, bool]]]
|
||||
turn_on_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
|
||||
turn_off_fn: Callable[[AdGuardHome], Callable[[], Coroutine[Any, Any, None]]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AdGuardHomeSwitchEntityDescription(
|
||||
SwitchEntityDescription, AdGuardHomeSwitchEntityDescriptionMixin
|
||||
):
|
||||
"""Describes AdGuard Home switch entity."""
|
||||
|
||||
|
||||
SWITCHES: tuple[AdGuardHomeSwitchEntityDescription, ...] = (
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="protection",
|
||||
name="Protection",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.protection_enabled,
|
||||
turn_on_fn=lambda adguard: adguard.enable_protection,
|
||||
turn_off_fn=lambda adguard: adguard.disable_protection,
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="parental",
|
||||
name="Parental control",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.parental.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.parental.enable,
|
||||
turn_off_fn=lambda adguard: adguard.parental.disable,
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="safesearch",
|
||||
name="Safe search",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.safesearch.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.safesearch.enable,
|
||||
turn_off_fn=lambda adguard: adguard.safesearch.disable,
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="safebrowsing",
|
||||
name="Safe browsing",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.safebrowsing.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.safebrowsing.enable,
|
||||
turn_off_fn=lambda adguard: adguard.safebrowsing.disable,
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="filtering",
|
||||
name="Filtering",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.filtering.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.filtering.enable,
|
||||
turn_off_fn=lambda adguard: adguard.filtering.disable,
|
||||
),
|
||||
AdGuardHomeSwitchEntityDescription(
|
||||
key="querylog",
|
||||
name="Query log",
|
||||
icon="mdi:shield-check",
|
||||
is_on_fn=lambda adguard: adguard.querylog.enabled,
|
||||
turn_on_fn=lambda adguard: adguard.querylog.enable,
|
||||
turn_off_fn=lambda adguard: adguard.querylog.disable,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
@@ -37,203 +104,46 @@ async def async_setup_entry(
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_ADGUARD_VERSION] = version
|
||||
|
||||
switches = [
|
||||
AdGuardHomeProtectionSwitch(adguard, entry),
|
||||
AdGuardHomeFilteringSwitch(adguard, entry),
|
||||
AdGuardHomeParentalSwitch(adguard, entry),
|
||||
AdGuardHomeSafeBrowsingSwitch(adguard, entry),
|
||||
AdGuardHomeSafeSearchSwitch(adguard, entry),
|
||||
AdGuardHomeQueryLogSwitch(adguard, entry),
|
||||
]
|
||||
async_add_entities(switches, True)
|
||||
async_add_entities(
|
||||
[AdGuardHomeSwitch(adguard, entry, description) for description in SWITCHES],
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class AdGuardHomeSwitch(AdGuardHomeDeviceEntity, SwitchEntity):
|
||||
class AdGuardHomeSwitch(AdGuardHomeEntity, SwitchEntity):
|
||||
"""Defines a AdGuard Home switch."""
|
||||
|
||||
entity_description: AdGuardHomeSwitchEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
adguard: AdGuardHome,
|
||||
entry: ConfigEntry,
|
||||
name: str,
|
||||
icon: str,
|
||||
key: str,
|
||||
enabled_default: bool = True,
|
||||
description: AdGuardHomeSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
self._state = False
|
||||
self._key = key
|
||||
super().__init__(adguard, entry, name, icon, enabled_default)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID for this sensor."""
|
||||
return "_".join(
|
||||
[DOMAIN, self.adguard.host, str(self.adguard.port), "switch", self._key]
|
||||
super().__init__(adguard, entry)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = "_".join(
|
||||
[DOMAIN, adguard.host, str(adguard.port), "switch", description.key]
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the switch."""
|
||||
return self._state
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn off the switch."""
|
||||
try:
|
||||
await self._adguard_turn_off()
|
||||
await self.entity_description.turn_off_fn(self.adguard)()
|
||||
except AdGuardHomeError:
|
||||
_LOGGER.error("An error occurred while turning off AdGuard Home switch")
|
||||
self._available = False
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
raise NotImplementedError()
|
||||
LOGGER.error("An error occurred while turning off AdGuard Home switch")
|
||||
self._attr_available = False
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the switch."""
|
||||
try:
|
||||
await self._adguard_turn_on()
|
||||
await self.entity_description.turn_on_fn(self.adguard)()
|
||||
except AdGuardHomeError:
|
||||
_LOGGER.error("An error occurred while turning on AdGuard Home switch")
|
||||
self._available = False
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class AdGuardHomeProtectionSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home protection switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard, entry, "AdGuard Protection", "mdi:shield-check", "protection"
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.disable_protection()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.enable_protection()
|
||||
LOGGER.error("An error occurred while turning on AdGuard Home switch")
|
||||
self._attr_available = False
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.protection_enabled()
|
||||
|
||||
|
||||
class AdGuardHomeParentalSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home parental control switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard, entry, "AdGuard Parental Control", "mdi:shield-check", "parental"
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.parental.disable()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.parental.enable()
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.parental.enabled()
|
||||
|
||||
|
||||
class AdGuardHomeSafeSearchSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home safe search switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard, entry, "AdGuard Safe Search", "mdi:shield-check", "safesearch"
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.safesearch.disable()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.safesearch.enable()
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.safesearch.enabled()
|
||||
|
||||
|
||||
class AdGuardHomeSafeBrowsingSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home safe search switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard, entry, "AdGuard Safe Browsing", "mdi:shield-check", "safebrowsing"
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.safebrowsing.disable()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.safebrowsing.enable()
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.safebrowsing.enabled()
|
||||
|
||||
|
||||
class AdGuardHomeFilteringSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home filtering switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard, entry, "AdGuard Filtering", "mdi:shield-check", "filtering"
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.filtering.disable()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.filtering.enable()
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.filtering.enabled()
|
||||
|
||||
|
||||
class AdGuardHomeQueryLogSwitch(AdGuardHomeSwitch):
|
||||
"""Defines a AdGuard Home query log switch."""
|
||||
|
||||
def __init__(self, adguard: AdGuardHome, entry: ConfigEntry) -> None:
|
||||
"""Initialize AdGuard Home switch."""
|
||||
super().__init__(
|
||||
adguard,
|
||||
entry,
|
||||
"AdGuard Query Log",
|
||||
"mdi:shield-check",
|
||||
"querylog",
|
||||
enabled_default=False,
|
||||
)
|
||||
|
||||
async def _adguard_turn_off(self) -> None:
|
||||
"""Turn off the switch."""
|
||||
await self.adguard.querylog.disable()
|
||||
|
||||
async def _adguard_turn_on(self) -> None:
|
||||
"""Turn on the switch."""
|
||||
await self.adguard.querylog.enable()
|
||||
|
||||
async def _adguard_update(self) -> None:
|
||||
"""Update AdGuard Home entity."""
|
||||
self._state = await self.adguard.querylog.enabled()
|
||||
self._attr_is_on = await self.entity_description.is_on_fn(self.adguard)()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
|
||||
"existing_instance_updated": "Friss\u00edtette a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3t."
|
||||
"existing_instance_updated": "A megl\u00e9v\u0151 konfigur\u00e1ci\u00f3 friss\u00edtve."
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "O servi\u00e7o j\u00e1 est\u00e1 configurado"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||
},
|
||||
@@ -14,7 +17,7 @@
|
||||
"port": "Porta",
|
||||
"ssl": "Utiliza um certificado SSL",
|
||||
"username": "Nome de Utilizador",
|
||||
"verify_ssl": "Verificar certificado SSL"
|
||||
"verify_ssl": "Verificar o certificado SSL"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"async_change": async_change,
|
||||
}
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -21,13 +21,13 @@ async def async_setup_entry(
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AdvantageAir motion platform."""
|
||||
"""Set up AdvantageAir Binary Sensor platform."""
|
||||
|
||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||
|
||||
entities: list[BinarySensorEntity] = []
|
||||
for ac_key, ac_device in instance["coordinator"].data["aircons"].items():
|
||||
entities.append(AdvantageAirZoneFilter(instance, ac_key))
|
||||
entities.append(AdvantageAirFilter(instance, ac_key))
|
||||
for zone_key, zone in ac_device["zones"].items():
|
||||
# Only add motion sensor when motion is enabled
|
||||
if zone["motionConfig"] >= 2:
|
||||
@@ -38,19 +38,17 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Filter."""
|
||||
class AdvantageAirFilter(AdvantageAirAcEntity, BinarySensorEntity):
|
||||
"""Advantage Air Filter sensor."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.PROBLEM
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_name = "Filter"
|
||||
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an Advantage Air Filter."""
|
||||
"""Initialize an Advantage Air Filter sensor."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = f'{self._ac["name"]} Filter'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-filter'
|
||||
)
|
||||
self._attr_unique_id += "-filter"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -58,18 +56,16 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
|
||||
return self._ac["filterCleanStatus"]
|
||||
|
||||
|
||||
class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone Motion."""
|
||||
class AdvantageAirZoneMotion(AdvantageAirZoneEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone Motion sensor."""
|
||||
|
||||
_attr_device_class = BinarySensorDeviceClass.MOTION
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Motion."""
|
||||
"""Initialize an Advantage Air Zone Motion sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Motion'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-motion'
|
||||
)
|
||||
self._attr_name = f'{self._zone["name"]} motion'
|
||||
self._attr_unique_id += "-motion"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
@@ -77,19 +73,17 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
|
||||
return self._zone["motion"] == 20
|
||||
|
||||
|
||||
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone MyZone."""
|
||||
class AdvantageAirZoneMyZone(AdvantageAirZoneEntity, BinarySensorEntity):
|
||||
"""Advantage Air Zone MyZone sensor."""
|
||||
|
||||
_attr_entity_registry_enabled_default = False
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone MyZone."""
|
||||
"""Initialize an Advantage Air Zone MyZone sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} MyZone'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-myzone'
|
||||
)
|
||||
self._attr_name = f'{self._zone["name"]} myZone'
|
||||
self._attr_unique_id += "-myzone"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
@@ -25,7 +24,7 @@ from .const import (
|
||||
ADVANTAGE_AIR_STATE_OPEN,
|
||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||
)
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||
|
||||
ADVANTAGE_AIR_HVAC_MODES = {
|
||||
"heat": HVACMode.HEAT,
|
||||
@@ -53,7 +52,6 @@ ADVANTAGE_AIR_FAN_MODES = {
|
||||
HASS_FAN_MODES = {v: k for k, v in ADVANTAGE_AIR_FAN_MODES.items()}
|
||||
FAN_SPEEDS = {FAN_LOW: 30, FAN_MEDIUM: 60, FAN_HIGH: 100}
|
||||
|
||||
ADVANTAGE_AIR_SERVICE_SET_MYZONE = "set_myzone"
|
||||
ZONE_HVAC_MODES = [HVACMode.OFF, HVACMode.HEAT_COOL]
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -79,26 +77,14 @@ async def async_setup_entry(
|
||||
entities.append(AdvantageAirZone(instance, ac_key, zone_key))
|
||||
async_add_entities(entities)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
ADVANTAGE_AIR_SERVICE_SET_MYZONE,
|
||||
{},
|
||||
"set_myzone",
|
||||
)
|
||||
|
||||
|
||||
class AdvantageAirClimateEntity(AdvantageAirEntity, ClimateEntity):
|
||||
"""AdvantageAir Climate class."""
|
||||
class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
"""AdvantageAir AC unit."""
|
||||
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
|
||||
|
||||
class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
"""AdvantageAir AC unit."""
|
||||
|
||||
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
|
||||
_attr_hvac_modes = AC_HVAC_MODES
|
||||
_attr_supported_features = (
|
||||
@@ -108,8 +94,6 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an AdvantageAir AC unit."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = self._ac["name"]
|
||||
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{ac_key}'
|
||||
if self._ac.get("myAutoModeEnabled"):
|
||||
self._attr_hvac_modes = AC_HVAC_MODES + [HVACMode.AUTO]
|
||||
|
||||
@@ -160,9 +144,13 @@ class AdvantageAirAC(AdvantageAirClimateEntity):
|
||||
await self.async_change({self.ac_key: {"info": {"setTemp": temp}}})
|
||||
|
||||
|
||||
class AdvantageAirZone(AdvantageAirClimateEntity):
|
||||
class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
||||
"""AdvantageAir Zone control."""
|
||||
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_hvac_modes = ZONE_HVAC_MODES
|
||||
_attr_supported_features = ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
|
||||
@@ -216,12 +204,3 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
|
||||
await self.async_change(
|
||||
{self.ac_key: {"zones": {self.zone_key: {"setTemp": temp}}}}
|
||||
)
|
||||
|
||||
async def set_myzone(self, **kwargs):
|
||||
"""Set this zone as the 'MyZone'."""
|
||||
_LOGGER.warning(
|
||||
"The advantage_air.set_myzone service has been deprecated and will be removed in a future version, please use the select.select_option service on the MyZone entity"
|
||||
)
|
||||
await self.async_change(
|
||||
{self.ac_key: {"info": {"myZone": self._zone["number"]}}}
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ from .const import (
|
||||
ADVANTAGE_AIR_STATE_OPEN,
|
||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||
)
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirZoneEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -39,8 +39,8 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
||||
"""Advantage Air Cover Class."""
|
||||
class AdvantageAirZoneVent(AdvantageAirZoneEntity, CoverEntity):
|
||||
"""Advantage Air Zone Vent."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.DAMPER
|
||||
_attr_supported_features = (
|
||||
@@ -50,12 +50,9 @@ class AdvantageAirZoneVent(AdvantageAirEntity, CoverEntity):
|
||||
)
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Cover Class."""
|
||||
"""Initialize an Advantage Air Zone Vent."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]}'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
|
||||
)
|
||||
self._attr_name = self._zone["name"]
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
|
||||
@@ -9,24 +9,46 @@ from .const import DOMAIN
|
||||
class AdvantageAirEntity(CoordinatorEntity):
|
||||
"""Parent class for Advantage Air Entities."""
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key=None):
|
||||
"""Initialize common aspects of an Advantage Air sensor."""
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, instance):
|
||||
"""Initialize common aspects of an Advantage Air entity."""
|
||||
super().__init__(instance["coordinator"])
|
||||
self._attr_unique_id = self.coordinator.data["system"]["rid"]
|
||||
|
||||
|
||||
class AdvantageAirAcEntity(AdvantageAirEntity):
|
||||
"""Parent class for Advantage Air AC Entities."""
|
||||
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize common aspects of an Advantage Air ac entity."""
|
||||
super().__init__(instance)
|
||||
self.async_change = instance["async_change"]
|
||||
self.ac_key = ac_key
|
||||
self.zone_key = zone_key
|
||||
self._attr_unique_id += f"-{ac_key}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
|
||||
via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
manufacturer="Advantage Air",
|
||||
model=self.coordinator.data["system"]["sysType"],
|
||||
name=self.coordinator.data["system"]["name"],
|
||||
sw_version=self.coordinator.data["system"]["myAppRev"],
|
||||
name=self.coordinator.data["aircons"][self.ac_key]["info"]["name"],
|
||||
)
|
||||
|
||||
@property
|
||||
def _ac(self):
|
||||
return self.coordinator.data["aircons"][self.ac_key]["info"]
|
||||
|
||||
|
||||
class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||
"""Parent class for Advantage Air Zone Entities."""
|
||||
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize common aspects of an Advantage Air zone entity."""
|
||||
super().__init__(instance, ac_key)
|
||||
self.zone_key = zone_key
|
||||
self._attr_unique_id += f"-{zone_key}"
|
||||
|
||||
@property
|
||||
def _zone(self):
|
||||
return self.coordinator.data["aircons"][self.ac_key]["zones"][self.zone_key]
|
||||
|
||||
@@ -5,7 +5,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirAcEntity
|
||||
|
||||
ADVANTAGE_AIR_INACTIVE = "Inactive"
|
||||
|
||||
@@ -15,7 +15,7 @@ async def async_setup_entry(
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AdvantageAir toggle platform."""
|
||||
"""Set up AdvantageAir select platform."""
|
||||
|
||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||
|
||||
@@ -25,21 +25,19 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
|
||||
class AdvantageAirMyZone(AdvantageAirAcEntity, SelectEntity):
|
||||
"""Representation of Advantage Air MyZone control."""
|
||||
|
||||
_attr_icon = "mdi:home-thermometer"
|
||||
_attr_options = [ADVANTAGE_AIR_INACTIVE]
|
||||
_number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
|
||||
_name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
|
||||
_attr_name = "MyZone"
|
||||
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an Advantage Air MyZone control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = f'{self._ac["name"]} MyZone'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-myzone'
|
||||
)
|
||||
self._attr_unique_id += "-myzone"
|
||||
|
||||
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
|
||||
if zone["type"] > 0:
|
||||
@@ -49,7 +47,7 @@ class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
|
||||
|
||||
@property
|
||||
def current_option(self):
|
||||
"""Return the fresh air status."""
|
||||
"""Return the current MyZone."""
|
||||
return self._number_to_name[self._ac["myZone"]]
|
||||
|
||||
async def async_select_option(self, option):
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirAcEntity, AdvantageAirZoneEntity
|
||||
|
||||
ADVANTAGE_AIR_SET_COUNTDOWN_VALUE = "minutes"
|
||||
ADVANTAGE_AIR_SET_COUNTDOWN_UNIT = "min"
|
||||
@@ -56,7 +56,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirTimeTo(AdvantageAirAcEntity, SensorEntity):
|
||||
"""Representation of Advantage Air timer control."""
|
||||
|
||||
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
|
||||
@@ -67,10 +67,8 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
super().__init__(instance, ac_key)
|
||||
self.action = action
|
||||
self._time_key = f"countDownTo{action}"
|
||||
self._attr_name = f'{self._ac["name"]} Time To {action}'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-timeto{action}'
|
||||
)
|
||||
self._attr_name = f"Time to {action}"
|
||||
self._attr_unique_id += f"-timeto{action}"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
@@ -90,7 +88,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
|
||||
await self.async_change({self.ac_key: {"info": {self._time_key: value}}})
|
||||
|
||||
|
||||
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneVent(AdvantageAirZoneEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone Vent Sensor."""
|
||||
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
@@ -100,10 +98,8 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Vent Sensor."""
|
||||
super().__init__(instance, ac_key, zone_key=zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Vent'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-vent'
|
||||
)
|
||||
self._attr_name = f'{self._zone["name"]} vent'
|
||||
self._attr_unique_id += "-vent"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
@@ -120,7 +116,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
|
||||
return "mdi:fan-off"
|
||||
|
||||
|
||||
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneSignal(AdvantageAirZoneEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone wireless signal sensor."""
|
||||
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
@@ -130,10 +126,8 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone wireless signal sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Signal'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
|
||||
)
|
||||
self._attr_name = f'{self._zone["name"]} signal'
|
||||
self._attr_unique_id += "-signal"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
@@ -154,7 +148,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
|
||||
return "mdi:wifi-strength-outline"
|
||||
|
||||
|
||||
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
|
||||
class AdvantageAirZoneTemp(AdvantageAirZoneEntity, SensorEntity):
|
||||
"""Representation of Advantage Air Zone temperature sensor."""
|
||||
|
||||
_attr_native_unit_of_measurement = TEMP_CELSIUS
|
||||
@@ -166,10 +160,8 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
|
||||
def __init__(self, instance, ac_key, zone_key):
|
||||
"""Initialize an Advantage Air Zone Temp Sensor."""
|
||||
super().__init__(instance, ac_key, zone_key)
|
||||
self._attr_name = f'{self._zone["name"]} Temperature'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
|
||||
)
|
||||
self._attr_name = f'{self._zone["name"]} temperature'
|
||||
self._attr_unique_id += "-temp"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
|
||||
@@ -15,11 +15,3 @@ set_time_to:
|
||||
min: 0
|
||||
max: 1440
|
||||
unit_of_measurement: minutes
|
||||
|
||||
set_myzone:
|
||||
name: Set MyZone
|
||||
description: Change which zone is set as the reference for temperature control (deprecated)
|
||||
target:
|
||||
entity:
|
||||
integration: advantage_air
|
||||
domain: climate
|
||||
|
||||
@@ -9,7 +9,7 @@ from .const import (
|
||||
ADVANTAGE_AIR_STATE_ON,
|
||||
DOMAIN as ADVANTAGE_AIR_DOMAIN,
|
||||
)
|
||||
from .entity import AdvantageAirEntity
|
||||
from .entity import AdvantageAirAcEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -17,7 +17,7 @@ async def async_setup_entry(
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up AdvantageAir toggle platform."""
|
||||
"""Set up AdvantageAir switch platform."""
|
||||
|
||||
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
|
||||
|
||||
@@ -28,18 +28,16 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AdvantageAirFreshAir(AdvantageAirEntity, SwitchEntity):
|
||||
class AdvantageAirFreshAir(AdvantageAirAcEntity, SwitchEntity):
|
||||
"""Representation of Advantage Air fresh air control."""
|
||||
|
||||
_attr_icon = "mdi:air-filter"
|
||||
_attr_name = "Fresh air"
|
||||
|
||||
def __init__(self, instance, ac_key):
|
||||
"""Initialize an Advantage Air fresh air control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_name = f'{self._ac["name"]} Fresh Air'
|
||||
self._attr_unique_id = (
|
||||
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-freshair'
|
||||
)
|
||||
self._attr_unique_id += "-freshair"
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
|
||||
@@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
ENTRY_WEATHER_COORDINATOR: weather_coordinator,
|
||||
}
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
|
||||
@@ -17,14 +17,6 @@ from homeassistant.components.weather import (
|
||||
ATTR_CONDITION_RAINY,
|
||||
ATTR_CONDITION_SNOWY,
|
||||
ATTR_CONDITION_SUNNY,
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
DEGREE,
|
||||
@@ -45,8 +37,16 @@ ENTRY_NAME = "name"
|
||||
ENTRY_WEATHER_COORDINATOR = "weather_coordinator"
|
||||
|
||||
ATTR_API_CONDITION = "condition"
|
||||
ATTR_API_FORECAST_CONDITION = "condition"
|
||||
ATTR_API_FORECAST_DAILY = "forecast-daily"
|
||||
ATTR_API_FORECAST_HOURLY = "forecast-hourly"
|
||||
ATTR_API_FORECAST_PRECIPITATION = "precipitation"
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY = "precipitation_probability"
|
||||
ATTR_API_FORECAST_TEMP = "temperature"
|
||||
ATTR_API_FORECAST_TEMP_LOW = "templow"
|
||||
ATTR_API_FORECAST_TIME = "datetime"
|
||||
ATTR_API_FORECAST_WIND_BEARING = "wind_bearing"
|
||||
ATTR_API_FORECAST_WIND_SPEED = "wind_speed"
|
||||
ATTR_API_HUMIDITY = "humidity"
|
||||
ATTR_API_PRESSURE = "pressure"
|
||||
ATTR_API_RAIN = "rain"
|
||||
@@ -158,14 +158,14 @@ CONDITIONS_MAP = {
|
||||
}
|
||||
|
||||
FORECAST_MONITORED_CONDITIONS = [
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
ATTR_API_FORECAST_CONDITION,
|
||||
ATTR_API_FORECAST_PRECIPITATION,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_API_FORECAST_TEMP,
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
]
|
||||
MONITORED_CONDITIONS = [
|
||||
ATTR_API_CONDITION,
|
||||
@@ -202,43 +202,43 @@ FORECAST_MODE_ATTR_API = {
|
||||
|
||||
FORECAST_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_CONDITION,
|
||||
key=ATTR_API_FORECAST_CONDITION,
|
||||
name="Condition",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_PRECIPITATION,
|
||||
key=ATTR_API_FORECAST_PRECIPITATION,
|
||||
name="Precipitation",
|
||||
native_unit_of_measurement=PRECIPITATION_MILLIMETERS_PER_HOUR,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
key=ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
name="Precipitation probability",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_TEMP,
|
||||
key=ATTR_API_FORECAST_TEMP,
|
||||
name="Temperature",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_TEMP_LOW,
|
||||
key=ATTR_API_FORECAST_TEMP_LOW,
|
||||
name="Temperature Low",
|
||||
native_unit_of_measurement=TEMP_CELSIUS,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_TIME,
|
||||
key=ATTR_API_FORECAST_TIME,
|
||||
name="Time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_WIND_BEARING,
|
||||
key=ATTR_API_FORECAST_WIND_BEARING,
|
||||
name="Wind bearing",
|
||||
native_unit_of_measurement=DEGREE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=ATTR_FORECAST_WIND_SPEED,
|
||||
key=ATTR_API_FORECAST_WIND_SPEED,
|
||||
name="Wind speed",
|
||||
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
|
||||
),
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
ENTRY_NAME,
|
||||
@@ -45,17 +45,13 @@ async def async_setup_entry(
|
||||
entities.extend(
|
||||
[
|
||||
AemetForecastSensor(
|
||||
name_prefix,
|
||||
unique_id_prefix,
|
||||
f"{domain_data[ENTRY_NAME]} {mode} Forecast",
|
||||
f"{unique_id}-forecast-{mode}",
|
||||
weather_coordinator,
|
||||
mode,
|
||||
description,
|
||||
)
|
||||
for mode in FORECAST_MODES
|
||||
if (
|
||||
(name_prefix := f"{domain_data[ENTRY_NAME]} {mode} Forecast")
|
||||
and (unique_id_prefix := f"{unique_id}-forecast-{mode}")
|
||||
)
|
||||
for description in FORECAST_SENSOR_TYPES
|
||||
if description.key in FORECAST_MONITORED_CONDITIONS
|
||||
]
|
||||
@@ -89,14 +85,14 @@ class AemetSensor(AbstractAemetSensor):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
unique_id,
|
||||
unique_id_prefix,
|
||||
weather_coordinator: WeatherUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(
|
||||
name=name,
|
||||
unique_id=f"{unique_id}-{description.key}",
|
||||
unique_id=f"{unique_id_prefix}-{description.key}",
|
||||
coordinator=weather_coordinator,
|
||||
description=description,
|
||||
)
|
||||
@@ -113,7 +109,7 @@ class AemetForecastSensor(AbstractAemetSensor):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
unique_id,
|
||||
unique_id_prefix,
|
||||
weather_coordinator: WeatherUpdateCoordinator,
|
||||
forecast_mode,
|
||||
description: SensorEntityDescription,
|
||||
@@ -121,7 +117,7 @@ class AemetForecastSensor(AbstractAemetSensor):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(
|
||||
name=name,
|
||||
unique_id=f"{unique_id}-{description.key}",
|
||||
unique_id=f"{unique_id_prefix}-{description.key}",
|
||||
coordinator=weather_coordinator,
|
||||
description=description,
|
||||
)
|
||||
@@ -139,6 +135,6 @@ class AemetForecastSensor(AbstractAemetSensor):
|
||||
)
|
||||
if forecasts:
|
||||
forecast = forecasts[0].get(self.entity_description.key)
|
||||
if self.entity_description.key == ATTR_FORECAST_TIME:
|
||||
if self.entity_description.key == ATTR_API_FORECAST_TIME:
|
||||
forecast = dt_util.parse_datetime(forecast)
|
||||
return forecast
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"api_key": "API\u30ad\u30fc",
|
||||
"latitude": "\u7def\u5ea6",
|
||||
"longitude": "\u7d4c\u5ea6",
|
||||
"name": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u540d\u524d"
|
||||
"name": "\u7d71\u5408\u306e\u540d\u524d"
|
||||
},
|
||||
"description": "AEMET OpenData\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
"description": "AEMET OpenData\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://opendata.aemet.es/centrodedescargas/altaUsuario \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
7
homeassistant/components/aemet/translations/pt.json
Normal file
7
homeassistant/components/aemet/translations/pt.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,36 @@
|
||||
"""Support for the AEMET OpenData service."""
|
||||
from homeassistant.components.weather import WeatherEntity
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS
|
||||
from homeassistant.const import (
|
||||
LENGTH_MILLIMETERS,
|
||||
PRESSURE_HPA,
|
||||
SPEED_KILOMETERS_PER_HOUR,
|
||||
TEMP_CELSIUS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
ATTR_API_CONDITION,
|
||||
ATTR_API_FORECAST_CONDITION,
|
||||
ATTR_API_FORECAST_PRECIPITATION,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_API_FORECAST_TEMP,
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PRESSURE,
|
||||
ATTR_API_TEMPERATURE,
|
||||
@@ -19,10 +42,32 @@ from .const import (
|
||||
ENTRY_WEATHER_COORDINATOR,
|
||||
FORECAST_MODE_ATTR_API,
|
||||
FORECAST_MODE_DAILY,
|
||||
FORECAST_MODE_HOURLY,
|
||||
FORECAST_MODES,
|
||||
)
|
||||
from .weather_update_coordinator import WeatherUpdateCoordinator
|
||||
|
||||
FORECAST_MAP = {
|
||||
FORECAST_MODE_DAILY: {
|
||||
ATTR_API_FORECAST_CONDITION: ATTR_FORECAST_CONDITION,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_API_FORECAST_TEMP_LOW: ATTR_FORECAST_NATIVE_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
},
|
||||
FORECAST_MODE_HOURLY: {
|
||||
ATTR_API_FORECAST_CONDITION: ATTR_FORECAST_CONDITION,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_API_FORECAST_PRECIPITATION: ATTR_FORECAST_NATIVE_PRECIPITATION,
|
||||
ATTR_API_FORECAST_TEMP: ATTR_FORECAST_NATIVE_TEMP,
|
||||
ATTR_API_FORECAST_TIME: ATTR_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING: ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_SPEED: ATTR_FORECAST_NATIVE_WIND_SPEED,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -47,9 +92,10 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
|
||||
"""Implementation of an AEMET OpenData sensor."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_temperature_unit = TEMP_CELSIUS
|
||||
_attr_pressure_unit = PRESSURE_HPA
|
||||
_attr_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
|
||||
_attr_native_precipitation_unit = LENGTH_MILLIMETERS
|
||||
_attr_native_pressure_unit = PRESSURE_HPA
|
||||
_attr_native_temperature_unit = TEMP_CELSIUS
|
||||
_attr_native_wind_speed_unit = SPEED_KILOMETERS_PER_HOUR
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -75,7 +121,12 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
|
||||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast array."""
|
||||
return self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]]
|
||||
forecasts = self.coordinator.data[FORECAST_MODE_ATTR_API[self._forecast_mode]]
|
||||
forecast_map = FORECAST_MAP[self._forecast_mode]
|
||||
return [
|
||||
{ha_key: forecast[api_key] for api_key, ha_key in forecast_map.items()}
|
||||
for forecast in forecasts
|
||||
]
|
||||
|
||||
@property
|
||||
def humidity(self):
|
||||
@@ -83,12 +134,12 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
|
||||
return self.coordinator.data[ATTR_API_HUMIDITY]
|
||||
|
||||
@property
|
||||
def pressure(self):
|
||||
def native_pressure(self):
|
||||
"""Return the pressure."""
|
||||
return self.coordinator.data[ATTR_API_PRESSURE]
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
def native_temperature(self):
|
||||
"""Return the temperature."""
|
||||
return self.coordinator.data[ATTR_API_TEMPERATURE]
|
||||
|
||||
@@ -98,6 +149,6 @@ class AemetWeather(CoordinatorEntity[WeatherUpdateCoordinator], WeatherEntity):
|
||||
return self.coordinator.data[ATTR_API_WIND_BEARING]
|
||||
|
||||
@property
|
||||
def wind_speed(self):
|
||||
def native_wind_speed(self):
|
||||
"""Return the wind speed."""
|
||||
return self.coordinator.data[ATTR_API_WIND_SPEED]
|
||||
|
||||
@@ -42,23 +42,21 @@ from aemet_opendata.helpers import (
|
||||
)
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.components.weather import (
|
||||
ATTR_FORECAST_CONDITION,
|
||||
ATTR_FORECAST_PRECIPITATION,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_FORECAST_TEMP,
|
||||
ATTR_FORECAST_TEMP_LOW,
|
||||
ATTR_FORECAST_TIME,
|
||||
ATTR_FORECAST_WIND_BEARING,
|
||||
ATTR_FORECAST_WIND_SPEED,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_API_CONDITION,
|
||||
ATTR_API_FORECAST_CONDITION,
|
||||
ATTR_API_FORECAST_DAILY,
|
||||
ATTR_API_FORECAST_HOURLY,
|
||||
ATTR_API_FORECAST_PRECIPITATION,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY,
|
||||
ATTR_API_FORECAST_TEMP,
|
||||
ATTR_API_FORECAST_TEMP_LOW,
|
||||
ATTR_API_FORECAST_TIME,
|
||||
ATTR_API_FORECAST_WIND_BEARING,
|
||||
ATTR_API_FORECAST_WIND_SPEED,
|
||||
ATTR_API_HUMIDITY,
|
||||
ATTR_API_PRESSURE,
|
||||
ATTR_API_RAIN,
|
||||
@@ -402,15 +400,15 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
return None
|
||||
|
||||
return {
|
||||
ATTR_FORECAST_CONDITION: condition,
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day(
|
||||
ATTR_API_FORECAST_CONDITION: condition,
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: self._get_precipitation_prob_day(
|
||||
day
|
||||
),
|
||||
ATTR_FORECAST_TEMP: self._get_temperature_day(day),
|
||||
ATTR_FORECAST_TEMP_LOW: self._get_temperature_low_day(day),
|
||||
ATTR_FORECAST_TIME: dt_util.as_utc(date).isoformat(),
|
||||
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed_day(day),
|
||||
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day),
|
||||
ATTR_API_FORECAST_TEMP: self._get_temperature_day(day),
|
||||
ATTR_API_FORECAST_TEMP_LOW: self._get_temperature_low_day(day),
|
||||
ATTR_API_FORECAST_TIME: dt_util.as_utc(date).isoformat(),
|
||||
ATTR_API_FORECAST_WIND_SPEED: self._get_wind_speed_day(day),
|
||||
ATTR_API_FORECAST_WIND_BEARING: self._get_wind_bearing_day(day),
|
||||
}
|
||||
|
||||
def _convert_forecast_hour(self, date, day, hour):
|
||||
@@ -420,15 +418,15 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
forecast_dt = date.replace(hour=hour, minute=0, second=0)
|
||||
|
||||
return {
|
||||
ATTR_FORECAST_CONDITION: condition,
|
||||
ATTR_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour),
|
||||
ATTR_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob(
|
||||
ATTR_API_FORECAST_CONDITION: condition,
|
||||
ATTR_API_FORECAST_PRECIPITATION: self._calc_precipitation(day, hour),
|
||||
ATTR_API_FORECAST_PRECIPITATION_PROBABILITY: self._calc_precipitation_prob(
|
||||
day, hour
|
||||
),
|
||||
ATTR_FORECAST_TEMP: self._get_temperature(day, hour),
|
||||
ATTR_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
|
||||
ATTR_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
|
||||
ATTR_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
|
||||
ATTR_API_FORECAST_TEMP: self._get_temperature(day, hour),
|
||||
ATTR_API_FORECAST_TIME: dt_util.as_utc(forecast_dt).isoformat(),
|
||||
ATTR_API_FORECAST_WIND_SPEED: self._get_wind_speed(day, hour),
|
||||
ATTR_API_FORECAST_WIND_BEARING: self._get_wind_bearing(day, hour),
|
||||
}
|
||||
|
||||
def _calc_precipitation(self, day, hour):
|
||||
|
||||
@@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
sw_version=agent_client.version,
|
||||
)
|
||||
|
||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Remove air_quality entities from registry if they exist
|
||||
ent_reg = er.async_get(hass)
|
||||
|
||||
@@ -25,7 +25,6 @@ SUFFIX_LIMIT: Final = "LIMIT"
|
||||
|
||||
ATTRIBUTION: Final = "Data provided by Airly"
|
||||
CONF_USE_NEAREST: Final = "use_nearest"
|
||||
DEFAULT_NAME: Final = "Airly"
|
||||
DOMAIN: Final = "airly"
|
||||
LABEL_ADVICE: Final = "advice"
|
||||
MANUFACTURER: Final = "Airly sp. z o.o."
|
||||
|
||||
@@ -45,7 +45,6 @@ from .const import (
|
||||
ATTR_LIMIT,
|
||||
ATTR_PERCENT,
|
||||
ATTRIBUTION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
SUFFIX_LIMIT,
|
||||
@@ -137,6 +136,7 @@ async def async_setup_entry(
|
||||
class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
||||
"""Define an Airly sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
entity_description: AirlySensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
@@ -151,12 +151,11 @@ class AirlySensor(CoordinatorEntity[AirlyDataUpdateCoordinator], SensorEntity):
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
|
||||
manufacturer=MANUFACTURER,
|
||||
name=DEFAULT_NAME,
|
||||
name=name,
|
||||
configuration_url=URL.format(
|
||||
latitude=coordinator.latitude, longitude=coordinator.longitude
|
||||
),
|
||||
)
|
||||
self._attr_name = f"{name} {description.name}"
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"longitude": "\u7d4c\u5ea6",
|
||||
"name": "\u540d\u524d"
|
||||
},
|
||||
"description": "Airly\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
"description": "API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://developer.airly.eu/register \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "Dost\u0119p do serwera Airly",
|
||||
"can_reach_server": "Dost\u0119p do serwera",
|
||||
"requests_per_day": "Dozwolone dzienne \u017c\u0105dania",
|
||||
"requests_remaining": "Pozosta\u0142o dozwolonych \u017c\u0105da\u0144"
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"longitude": "\u7d4c\u5ea6",
|
||||
"radius": "\u30b9\u30c6\u30fc\u30b7\u30e7\u30f3\u534a\u5f84(\u30de\u30a4\u30eb; \u30aa\u30d7\u30b7\u30e7\u30f3)"
|
||||
},
|
||||
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
"description": "AirNow\u306e\u7a7a\u6c17\u54c1\u8cea\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u3002 API\u30ad\u30fc\u3092\u751f\u6210\u3059\u308b\u306b\u306f\u3001https://docs.airnowapi.org/account/request/ \u306b\u30a2\u30af\u30bb\u30b9\u3057\u3066\u304f\u3060\u3055\u3044"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
7
homeassistant/components/airthings/translations/pt.json
Normal file
7
homeassistant/components/airthings/translations/pt.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"error": {
|
||||
"cannot_connect": "Falha na liga\u00e7\u00e3o"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
11
homeassistant/components/airtouch4/translations/pt.json
Normal file
11
homeassistant/components/airtouch4/translations/pt.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Anfitri\u00e3o"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
from collections.abc import Mapping
|
||||
from datetime import timedelta
|
||||
from math import ceil
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from pyairvisual import CloudAPI, NodeSamba
|
||||
from pyairvisual.errors import (
|
||||
@@ -12,6 +12,7 @@ from pyairvisual.errors import (
|
||||
InvalidKeyError,
|
||||
KeyExpiredError,
|
||||
NodeProError,
|
||||
UnauthorizedError,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -210,9 +211,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
|
||||
try:
|
||||
data = await api_coro
|
||||
return cast(dict[str, Any], data)
|
||||
except (InvalidKeyError, KeyExpiredError) as ex:
|
||||
return await api_coro
|
||||
except (InvalidKeyError, KeyExpiredError, UnauthorizedError) as ex:
|
||||
raise ConfigEntryAuthFailed from ex
|
||||
except AirVisualError as err:
|
||||
raise UpdateFailed(f"Error while retrieving data: {err}") from err
|
||||
@@ -253,8 +253,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async with NodeSamba(
|
||||
entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD]
|
||||
) as node:
|
||||
data = await node.async_get_latest_measurements()
|
||||
return cast(dict[str, Any], data)
|
||||
return await node.async_get_latest_measurements()
|
||||
except NodeProError as err:
|
||||
raise UpdateFailed(f"Error while retrieving data: {err}") from err
|
||||
|
||||
@@ -274,7 +273,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
if CONF_API_KEY in entry.data:
|
||||
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ from pyairvisual import CloudAPI, NodeSamba
|
||||
from pyairvisual.errors import (
|
||||
AirVisualError,
|
||||
InvalidKeyError,
|
||||
KeyExpiredError,
|
||||
NodeProError,
|
||||
NotFoundError,
|
||||
UnauthorizedError,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -119,7 +121,7 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input[CONF_API_KEY] not in valid_keys:
|
||||
try:
|
||||
await coro
|
||||
except InvalidKeyError:
|
||||
except (InvalidKeyError, KeyExpiredError, UnauthorizedError):
|
||||
errors[CONF_API_KEY] = "invalid_api_key"
|
||||
except NotFoundError:
|
||||
errors[CONF_CITY] = "location_not_found"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "AirVisual",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airvisual",
|
||||
"requirements": ["pyairvisual==5.0.9"],
|
||||
"requirements": ["pyairvisual==2022.07.0"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyairvisual", "pysmb"]
|
||||
|
||||
@@ -62,20 +62,20 @@ SENSOR_KIND_VOC = "voc"
|
||||
GEOGRAPHY_SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_LEVEL,
|
||||
name="Air Pollution Level",
|
||||
name="Air pollution level",
|
||||
device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
|
||||
icon="mdi:gauge",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_AQI,
|
||||
name="Air Quality Index",
|
||||
name="Air quality index",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
native_unit_of_measurement="AQI",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_POLLUTANT,
|
||||
name="Main Pollutant",
|
||||
name="Main pollutant",
|
||||
device_class=DEVICE_CLASS_POLLUTANT_LABEL,
|
||||
icon="mdi:chemical-weapon",
|
||||
),
|
||||
@@ -85,7 +85,7 @@ GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
|
||||
NODE_PRO_SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=SENSOR_KIND_AQI,
|
||||
name="Air Quality Index",
|
||||
name="Air quality index",
|
||||
device_class=SensorDeviceClass.AQI,
|
||||
native_unit_of_measurement="AQI",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -292,6 +292,8 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
|
||||
class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
"""Define an AirVisual sensor related to a Node/Pro unit."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DataUpdateCoordinator,
|
||||
@@ -301,9 +303,6 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, description)
|
||||
|
||||
self._attr_name = (
|
||||
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
|
||||
)
|
||||
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
|
||||
|
||||
@property
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
"invalid_api_key": "Chave de API inv\u00e1lida"
|
||||
},
|
||||
"step": {
|
||||
"geography_by_coords": {
|
||||
"data": {
|
||||
"latitude": "Latitude"
|
||||
}
|
||||
},
|
||||
"node_pro": {
|
||||
"data": {
|
||||
"ip_address": "Servidor",
|
||||
@@ -18,7 +23,7 @@
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": ""
|
||||
"api_key": "Chave da API"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"state": {
|
||||
"airvisual__pollutant_label": {
|
||||
"co": "Mon\u00f3xido de carbono",
|
||||
"n2": "Di\u00f3xido de nitrog\u00e9nio",
|
||||
"o3": "Ozono",
|
||||
"p1": "PM10"
|
||||
},
|
||||
"airvisual__pollutant_level": {
|
||||
"moderate": "Moderado"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Airzone",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"requirements": ["aioairzone==0.4.5"],
|
||||
"requirements": ["aioairzone==0.4.6"],
|
||||
"codeowners": ["@Noltari"],
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"]
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"host": "\u30db\u30b9\u30c8",
|
||||
"port": "\u30dd\u30fc\u30c8"
|
||||
},
|
||||
"description": "Airzone\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
|
||||
"description": "Airzone\u7d71\u5408\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
homeassistant/components/airzone/translations/pt.json
Normal file
12
homeassistant/components/airzone/translations/pt.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Servidor",
|
||||
"port": "Porta"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,18 +12,20 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CLIENT_ID, DOMAIN
|
||||
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.COVER]
|
||||
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up platform from a ConfigEntry."""
|
||||
username = entry.data[CONF_USERNAME]
|
||||
password = entry.data[CONF_PASSWORD]
|
||||
acc = AladdinConnectClient(username, password, async_get_clientsession(hass))
|
||||
acc = AladdinConnectClient(
|
||||
username, password, async_get_clientsession(hass), CLIENT_ID
|
||||
)
|
||||
try:
|
||||
if not await acc.login():
|
||||
raise ConfigEntryAuthFailed("Incorrect Password")
|
||||
@@ -31,7 +33,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
raise ConfigEntryNotReady("Can not connect to host") from ex
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CLIENT_ID, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,7 +38,10 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
acc = AladdinConnectClient(
|
||||
data[CONF_USERNAME], data[CONF_PASSWORD], async_get_clientsession(hass)
|
||||
data[CONF_USERNAME],
|
||||
data[CONF_PASSWORD],
|
||||
async_get_clientsession(hass),
|
||||
CLIENT_ID,
|
||||
)
|
||||
login = await acc.login()
|
||||
await acc.close()
|
||||
|
||||
@@ -18,3 +18,4 @@ STATES_MAP: Final[dict[str, str]] = {
|
||||
|
||||
DOMAIN = "aladdin_connect"
|
||||
SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
CLIENT_ID = "1000"
|
||||
|
||||
@@ -24,6 +24,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
@@ -87,8 +88,19 @@ class AladdinDevice(CoverEntity):
|
||||
|
||||
self._device_id = device["device_id"]
|
||||
self._number = device["door_number"]
|
||||
self._attr_name = device["name"]
|
||||
self._name = device["name"]
|
||||
self._serial = device["serial"]
|
||||
self._attr_unique_id = f"{self._device_id}-{self._number}"
|
||||
self._attr_has_entity_name = True
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo | None:
|
||||
"""Device information for Aladdin Connect cover."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device_id)},
|
||||
name=self._name,
|
||||
manufacturer="Overhead Door",
|
||||
)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Connect Aladdin Connect to the cloud."""
|
||||
@@ -97,8 +109,8 @@ class AladdinDevice(CoverEntity):
|
||||
"""Schedule a state update."""
|
||||
self.async_write_ha_state()
|
||||
|
||||
self._acc.register_callback(update_callback, self._number)
|
||||
await self._acc.get_doors(self._number)
|
||||
self._acc.register_callback(update_callback, self._serial)
|
||||
await self._acc.get_doors(self._serial)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Close Aladdin Connect before removing."""
|
||||
@@ -114,7 +126,7 @@ class AladdinDevice(CoverEntity):
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update status of cover."""
|
||||
await self._acc.get_doors(self._number)
|
||||
await self._acc.get_doors(self._serial)
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "aladdin_connect",
|
||||
"name": "Aladdin Connect",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"requirements": ["AIOAladdinConnect==0.1.21"],
|
||||
"requirements": ["AIOAladdinConnect==0.1.41"],
|
||||
"codeowners": ["@mkmer"],
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aladdin_connect"],
|
||||
|
||||
@@ -11,3 +11,4 @@ class DoorDevice(TypedDict):
|
||||
door_number: int
|
||||
name: str
|
||||
status: str
|
||||
serial: str
|
||||
|
||||
116
homeassistant/components/aladdin_connect/sensor.py
Normal file
116
homeassistant/components/aladdin_connect/sensor.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Support for Aladdin Connect Garage Door sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import cast
|
||||
|
||||
from AIOAladdinConnect import AladdinConnectClient
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .model import DoorDevice
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccSensorEntityDescriptionMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccSensorEntityDescription(
|
||||
SensorEntityDescription, AccSensorEntityDescriptionMixin
|
||||
):
|
||||
"""Describes AladdinConnect sensor entity."""
|
||||
|
||||
|
||||
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
AccSensorEntityDescription(
|
||||
key="battery_level",
|
||||
name="Battery level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=AladdinConnectClient.get_battery_status,
|
||||
),
|
||||
AccSensorEntityDescription(
|
||||
key="rssi",
|
||||
name="Wi-Fi RSSI",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=AladdinConnectClient.get_rssi_status,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up Aladdin Connect sensor devices."""
|
||||
|
||||
acc: AladdinConnectClient = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities = []
|
||||
doors = await acc.get_doors()
|
||||
|
||||
for door in doors:
|
||||
entities.extend(
|
||||
[AladdinConnectSensor(acc, door, description) for description in SENSORS]
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AladdinConnectSensor(SensorEntity):
|
||||
"""A sensor implementation for Aladdin Connect devices."""
|
||||
|
||||
_device: AladdinConnectSensor
|
||||
entity_description: AccSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
acc: AladdinConnectClient,
|
||||
device: DoorDevice,
|
||||
description: AccSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a sensor for an Abode device."""
|
||||
self._device_id = device["device_id"]
|
||||
self._number = device["door_number"]
|
||||
self._name = device["name"]
|
||||
self._acc = acc
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
|
||||
self._attr_has_entity_name = True
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo | None:
|
||||
"""Device information for Aladdin Connect sensors."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._device_id)},
|
||||
name=self._name,
|
||||
manufacturer="Overhead Door",
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
return cast(
|
||||
float,
|
||||
self.entity_description.value_fn(self._acc, self._device_id, self._number),
|
||||
)
|
||||
@@ -13,8 +13,8 @@
|
||||
"data": {
|
||||
"password": "\u30d1\u30b9\u30ef\u30fc\u30c9"
|
||||
},
|
||||
"description": "Aladdin Connect\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
|
||||
"title": "\u30a4\u30f3\u30c6\u30b0\u30ec\u30fc\u30b7\u30e7\u30f3\u306e\u518d\u8a8d\u8a3c"
|
||||
"description": "Aladdin Connect\u7d71\u5408\u3067\u306f\u3001\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u518d\u8a8d\u8a3c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
|
||||
"title": "\u7d71\u5408\u306e\u518d\u8a8d\u8a3c"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"reauth_successful": "Reautentica\u00e7\u00e3o bem sucedida"
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Autentica\u00e7\u00e3o inv\u00e1lida"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "Nome de Utilizador"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"armed_custom_bypass": "Armado com desvio personalizado",
|
||||
"armed_home": "Armado Casa",
|
||||
"armed_night": "Armado noite",
|
||||
"armed_vacation": "Armado f\u00e9rias",
|
||||
"arming": "A armar",
|
||||
"disarmed": "Desarmado",
|
||||
"disarming": "A desarmar",
|
||||
|
||||
@@ -131,7 +131,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await open_connection()
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
|
||||
},
|
||||
"step": {
|
||||
"protocol": {
|
||||
"data": {
|
||||
|
||||
@@ -903,6 +903,7 @@ class AlexaContactSensor(AlexaCapability):
|
||||
"en-CA",
|
||||
"en-IN",
|
||||
"en-US",
|
||||
"en-GB",
|
||||
"es-ES",
|
||||
"it-IT",
|
||||
"ja-JP",
|
||||
@@ -951,6 +952,7 @@ class AlexaMotionSensor(AlexaCapability):
|
||||
"en-CA",
|
||||
"en-IN",
|
||||
"en-US",
|
||||
"en-GB",
|
||||
"es-ES",
|
||||
"it-IT",
|
||||
"ja-JP",
|
||||
|
||||
@@ -65,6 +65,7 @@ class AbstractConfig(ABC):
|
||||
|
||||
async def async_enable_proactive_mode(self):
|
||||
"""Enable proactive mode."""
|
||||
_LOGGER.debug("Enable proactive mode")
|
||||
if self._unsub_proactive_report is None:
|
||||
self._unsub_proactive_report = self.hass.async_create_task(
|
||||
async_enable_proactive_mode(self.hass, self)
|
||||
@@ -77,6 +78,7 @@ class AbstractConfig(ABC):
|
||||
|
||||
async def async_disable_proactive_mode(self):
|
||||
"""Disable proactive mode."""
|
||||
_LOGGER.debug("Disable proactive mode")
|
||||
if unsub_func := await self._unsub_proactive_report:
|
||||
unsub_func()
|
||||
self._unsub_proactive_report = None
|
||||
@@ -113,7 +115,6 @@ class AbstractConfig(ABC):
|
||||
self._store.set_authorized(authorized)
|
||||
if self.should_report_state != self.is_reporting_states:
|
||||
if self.should_report_state:
|
||||
_LOGGER.debug("Enable proactive mode")
|
||||
try:
|
||||
await self.async_enable_proactive_mode()
|
||||
except Exception:
|
||||
@@ -121,7 +122,6 @@ class AbstractConfig(ABC):
|
||||
self._store.set_authorized(False)
|
||||
raise
|
||||
else:
|
||||
_LOGGER.debug("Disable proactive mode")
|
||||
await self.async_disable_proactive_mode()
|
||||
|
||||
|
||||
|
||||
@@ -68,16 +68,19 @@ API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"}
|
||||
# back to HA state.
|
||||
API_THERMOSTAT_MODES = OrderedDict(
|
||||
[
|
||||
(climate.HVAC_MODE_HEAT, "HEAT"),
|
||||
(climate.HVAC_MODE_COOL, "COOL"),
|
||||
(climate.HVAC_MODE_HEAT_COOL, "AUTO"),
|
||||
(climate.HVAC_MODE_AUTO, "AUTO"),
|
||||
(climate.HVAC_MODE_OFF, "OFF"),
|
||||
(climate.HVAC_MODE_FAN_ONLY, "OFF"),
|
||||
(climate.HVAC_MODE_DRY, "CUSTOM"),
|
||||
(climate.HVACMode.HEAT, "HEAT"),
|
||||
(climate.HVACMode.COOL, "COOL"),
|
||||
(climate.HVACMode.HEAT_COOL, "AUTO"),
|
||||
(climate.HVACMode.AUTO, "AUTO"),
|
||||
(climate.HVACMode.OFF, "OFF"),
|
||||
(climate.HVACMode.FAN_ONLY, "CUSTOM"),
|
||||
(climate.HVACMode.DRY, "CUSTOM"),
|
||||
]
|
||||
)
|
||||
API_THERMOSTAT_MODES_CUSTOM = {climate.HVAC_MODE_DRY: "DEHUMIDIFY"}
|
||||
API_THERMOSTAT_MODES_CUSTOM = {
|
||||
climate.HVACMode.DRY: "DEHUMIDIFY",
|
||||
climate.HVACMode.FAN_ONLY: "FAN",
|
||||
}
|
||||
API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"}
|
||||
|
||||
# AlexaModeController does not like a single mode for the fan preset, we add PRESET_MODE_NA if a fan has only one preset_mode
|
||||
|
||||
@@ -12,7 +12,6 @@ from .auth import Auth
|
||||
from .config import AbstractConfig
|
||||
from .const import CONF_ENDPOINT, CONF_ENTITY_CONFIG, CONF_FILTER, CONF_LOCALE
|
||||
from .smart_home import async_handle_message
|
||||
from .state_report import async_enable_proactive_mode
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home"
|
||||
@@ -104,7 +103,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> None:
|
||||
hass.http.register_view(SmartHomeView(smart_home_config))
|
||||
|
||||
if smart_home_config.should_report_state:
|
||||
await async_enable_proactive_mode(hass, smart_home_config)
|
||||
await smart_home_config.async_enable_proactive_mode()
|
||||
|
||||
|
||||
class SmartHomeView(HomeAssistantView):
|
||||
|
||||
@@ -86,7 +86,9 @@ async def async_enable_proactive_mode(hass, smart_home_config):
|
||||
return
|
||||
|
||||
if should_doorbell:
|
||||
if new_state.state == STATE_ON:
|
||||
if new_state.state == STATE_ON and (
|
||||
old_state is None or old_state.state != STATE_ON
|
||||
):
|
||||
await async_send_doorbell_event_message(
|
||||
hass, smart_home_config, alexa_changed_entity
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, cast
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError, ClientSession
|
||||
import async_timeout
|
||||
@@ -167,8 +167,8 @@ async def _configure_almond_for_ha(
|
||||
return
|
||||
|
||||
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
|
||||
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
data = cast(Optional[dict], await store.async_load())
|
||||
store = storage.Store[dict[str, Any]](hass, STORAGE_VERSION, STORAGE_KEY)
|
||||
data = await store.async_load()
|
||||
|
||||
if data is None:
|
||||
data = {}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"cannot_connect": "\u63a5\u7d9a\u306b\u5931\u6557\u3057\u307e\u3057\u305f",
|
||||
"missing_configuration": "\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8\u304c\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u306b\u5f93\u3063\u3066\u304f\u3060\u3055\u3044\u3002",
|
||||
"no_url_available": "\u4f7f\u7528\u53ef\u80fd\u306aURL\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u3053\u306e\u30a8\u30e9\u30fc\u306e\u8a73\u7d30\u306b\u3064\u3044\u3066\u306f\u3001[\u30d8\u30eb\u30d7\u30bb\u30af\u30b7\u30e7\u30f3\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044]({docs_url})",
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u5358\u4e00\u306e\u8a2d\u5b9a\u3057\u304b\u3067\u304d\u307e\u305b\u3093\u3002"
|
||||
"single_instance_allowed": "\u3059\u3067\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u8a2d\u5b9a\u3067\u304d\u308b\u306e\u306f1\u3064\u3060\u3051\u3067\u3059\u3002"
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
|
||||
@@ -3,10 +3,16 @@ from __future__ import annotations
|
||||
|
||||
from ambee import AirQuality, Ambee, AmbeeAuthenticationError, Pollen
|
||||
|
||||
from homeassistant.components.repairs import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
|
||||
@@ -14,6 +20,20 @@ from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_P
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Ambee integration."""
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"pending_removal",
|
||||
breaks_in_ha_version="2022.10.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="pending_removal",
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Ambee from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
|
||||
@@ -58,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await pollen.async_config_entry_first_refresh()
|
||||
hass.data[DOMAIN][entry.entry_id][SERVICE_POLLEN] = pollen
|
||||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
@@ -67,4 +87,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
if not hass.data[DOMAIN]:
|
||||
async_delete_issue(hass, DOMAIN, "pending_removal")
|
||||
return unload_ok
|
||||
|
||||
@@ -27,7 +27,7 @@ SERVICE_AIR_QUALITY: Final = "air_quality"
|
||||
SERVICE_POLLEN: Final = "pollen"
|
||||
|
||||
SERVICES: dict[str, str] = {
|
||||
SERVICE_AIR_QUALITY: "Air Quality",
|
||||
SERVICE_AIR_QUALITY: "Air quality",
|
||||
SERVICE_POLLEN: "Pollen",
|
||||
}
|
||||
|
||||
@@ -35,25 +35,25 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
SERVICE_AIR_QUALITY: [
|
||||
SensorEntityDescription(
|
||||
key="particulate_matter_2_5",
|
||||
name="Particulate Matter < 2.5 μm",
|
||||
name="Particulate matter < 2.5 μm",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="particulate_matter_10",
|
||||
name="Particulate Matter < 10 μm",
|
||||
name="Particulate matter < 10 μm",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="sulphur_dioxide",
|
||||
name="Sulphur Dioxide (SO2)",
|
||||
name="Sulphur dioxide (SO2)",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="nitrogen_dioxide",
|
||||
name="Nitrogen Dioxide (NO2)",
|
||||
name="Nitrogen dioxide (NO2)",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -65,60 +65,60 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="carbon_monoxide",
|
||||
name="Carbon Monoxide (CO)",
|
||||
name="Carbon monoxide (CO)",
|
||||
device_class=SensorDeviceClass.CO,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="air_quality_index",
|
||||
name="Air Quality Index (AQI)",
|
||||
name="Air quality index (AQI)",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
],
|
||||
SERVICE_POLLEN: [
|
||||
SensorEntityDescription(
|
||||
key="grass",
|
||||
name="Grass Pollen",
|
||||
name="Grass",
|
||||
icon="mdi:grass",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree",
|
||||
name="Tree Pollen",
|
||||
name="Tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed",
|
||||
name="Weed Pollen",
|
||||
name="Weed",
|
||||
icon="mdi:sprout",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="grass_risk",
|
||||
name="Grass Pollen Risk",
|
||||
name="Grass risk",
|
||||
icon="mdi:grass",
|
||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_risk",
|
||||
name="Tree Pollen Risk",
|
||||
name="Tree risk",
|
||||
icon="mdi:tree",
|
||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed_risk",
|
||||
name="Weed Pollen Risk",
|
||||
name="Weed risk",
|
||||
icon="mdi:sprout",
|
||||
device_class=DEVICE_CLASS_AMBEE_RISK,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="grass_poaceae",
|
||||
name="Poaceae Grass Pollen",
|
||||
name="Poaceae grass",
|
||||
icon="mdi:grass",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -126,7 +126,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_alder",
|
||||
name="Alder Tree Pollen",
|
||||
name="Alder tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -134,7 +134,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_birch",
|
||||
name="Birch Tree Pollen",
|
||||
name="Birch tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -142,7 +142,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_cypress",
|
||||
name="Cypress Tree Pollen",
|
||||
name="Cypress tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -150,7 +150,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_elm",
|
||||
name="Elm Tree Pollen",
|
||||
name="Elm tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -158,7 +158,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_hazel",
|
||||
name="Hazel Tree Pollen",
|
||||
name="Hazel tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -166,7 +166,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_oak",
|
||||
name="Oak Tree Pollen",
|
||||
name="Oak tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -174,7 +174,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_pine",
|
||||
name="Pine Tree Pollen",
|
||||
name="Pine tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -182,7 +182,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_plane",
|
||||
name="Plane Tree Pollen",
|
||||
name="Plane tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -190,7 +190,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="tree_poplar",
|
||||
name="Poplar Tree Pollen",
|
||||
name="Poplar tree",
|
||||
icon="mdi:tree",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -198,7 +198,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed_chenopod",
|
||||
name="Chenopod Weed Pollen",
|
||||
name="Chenopod weed",
|
||||
icon="mdi:sprout",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -206,7 +206,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed_mugwort",
|
||||
name="Mugwort Weed Pollen",
|
||||
name="Mugwort weed",
|
||||
icon="mdi:sprout",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -214,7 +214,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed_nettle",
|
||||
name="Nettle Weed Pollen",
|
||||
name="Nettle weed",
|
||||
icon="mdi:sprout",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
@@ -222,7 +222,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="weed_ragweed",
|
||||
name="Ragweed Weed Pollen",
|
||||
name="Ragweed weed",
|
||||
icon="mdi:sprout",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user