mirror of
https://github.com/home-assistant/core.git
synced 2025-09-22 11:29:33 +00:00
Compare commits
2690 Commits
manual_tri
...
2025.5.0b2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
16f36912db | ||
![]() |
2a5f031ba5 | ||
![]() |
71bb8ae529 | ||
![]() |
f6a94d0661 | ||
![]() |
c2575735ff | ||
![]() |
7eee5ecd9a | ||
![]() |
db0cf9fbf4 | ||
![]() |
aded44ee0f | ||
![]() |
901926e8e6 | ||
![]() |
2e336626ac | ||
![]() |
3ea3d77f4d | ||
![]() |
fe8e7b73bf | ||
![]() |
a34065ee2f | ||
![]() |
e1a908c8ac | ||
![]() |
628d99886a | ||
![]() |
6b10710484 | ||
![]() |
8a4f28fa94 | ||
![]() |
e7331633c7 | ||
![]() |
934be08a59 | ||
![]() |
485522fd76 | ||
![]() |
eba0daa2e9 | ||
![]() |
851779e7ad | ||
![]() |
ea9a0f4bf5 | ||
![]() |
1143468eb5 | ||
![]() |
52b0b1e2ab | ||
![]() |
23ba652b83 | ||
![]() |
43b737c4a2 | ||
![]() |
3fbd23b98d | ||
![]() |
0cbeeebd0b | ||
![]() |
99a0679ee9 | ||
![]() |
e82713b68c | ||
![]() |
9293afd95a | ||
![]() |
9ea3e786f6 | ||
![]() |
a8169d2056 | ||
![]() |
1cd94affd1 | ||
![]() |
724825d34c | ||
![]() |
c3abf5a190 | ||
![]() |
621cf6ce58 | ||
![]() |
83e0ed7b05 | ||
![]() |
0752807aaf | ||
![]() |
53df69ee6e | ||
![]() |
dbc38cdc6b | ||
![]() |
102d55ec57 | ||
![]() |
3f7cae8583 | ||
![]() |
a3a1d424c6 | ||
![]() |
2cede8fec6 | ||
![]() |
5816d495e3 | ||
![]() |
24803b1e75 | ||
![]() |
9732b8c0dd | ||
![]() |
1626b3b7c9 | ||
![]() |
8760a82dfa | ||
![]() |
0f5d5ab0a2 | ||
![]() |
e05f7a9633 | ||
![]() |
30656a4e72 | ||
![]() |
5ccb9486e0 | ||
![]() |
a6d5891e8a | ||
![]() |
fc440f310b | ||
![]() |
4d9ab42ab5 | ||
![]() |
e53f380710 | ||
![]() |
6a514ac2de | ||
![]() |
02bd8d67c8 | ||
![]() |
2c118d4850 | ||
![]() |
949225ffeb | ||
![]() |
1ef04a8dde | ||
![]() |
5c58f97e57 | ||
![]() |
f1b8c8855e | ||
![]() |
daf143f66e | ||
![]() |
6c0e46f050 | ||
![]() |
70133da025 | ||
![]() |
837592381a | ||
![]() |
101b073793 | ||
![]() |
df5f150531 | ||
![]() |
4061314cd2 | ||
![]() |
819be719ef | ||
![]() |
80e4f19172 | ||
![]() |
5b0ea21607 | ||
![]() |
84634ce288 | ||
![]() |
fa1dc75517 | ||
![]() |
d8122d149b | ||
![]() |
923300f4e7 | ||
![]() |
af66d0b647 | ||
![]() |
8b9c4dadd0 | ||
![]() |
857db679ae | ||
![]() |
03ecd7f06c | ||
![]() |
c6bdee8dd8 | ||
![]() |
800f403643 | ||
![]() |
9b1c6b07f5 | ||
![]() |
f7c1a0c5e6 | ||
![]() |
57a7c26c64 | ||
![]() |
d606e86b47 | ||
![]() |
f7a9319122 | ||
![]() |
b16151ac6d | ||
![]() |
bdd9099294 | ||
![]() |
d924f0b1d6 | ||
![]() |
5dab9ba01b | ||
![]() |
ae118da5a1 | ||
![]() |
e24082be9a | ||
![]() |
8fafbfaf82 | ||
![]() |
6168fe006e | ||
![]() |
6c633668f6 | ||
![]() |
73a1dbffeb | ||
![]() |
40217e764d | ||
![]() |
a7af0eaccd | ||
![]() |
4ac29c6aef | ||
![]() |
ef023f084b | ||
![]() |
441bca5bda | ||
![]() |
a8bee20aa3 | ||
![]() |
04bea9c732 | ||
![]() |
98cbc2a182 | ||
![]() |
09518b1a71 | ||
![]() |
42d22bb1a3 | ||
![]() |
69c387a360 | ||
![]() |
4b6fa12925 | ||
![]() |
c562cba030 | ||
![]() |
40764b6995 | ||
![]() |
4ee3290929 | ||
![]() |
dc02c37413 | ||
![]() |
34becb541a | ||
![]() |
eabf88e3c9 | ||
![]() |
c3dac50f21 | ||
![]() |
f7240b52c5 | ||
![]() |
2112b5a763 | ||
![]() |
03b10b45c4 | ||
![]() |
62361230f3 | ||
![]() |
653306eb91 | ||
![]() |
07e2cfb736 | ||
![]() |
f980434046 | ||
![]() |
97084e9382 | ||
![]() |
9db34fe232 | ||
![]() |
c4f0b4ab23 | ||
![]() |
1647afc58a | ||
![]() |
53ea8422f8 | ||
![]() |
0b988b3fac | ||
![]() |
5a4abe3ec1 | ||
![]() |
89abc5ac69 | ||
![]() |
08fe6653bb | ||
![]() |
9aa18c7157 | ||
![]() |
cc7929f8fb | ||
![]() |
d657298791 | ||
![]() |
05f393560f | ||
![]() |
92da640d4c | ||
![]() |
ad3fd151aa | ||
![]() |
cd104dc08c | ||
![]() |
d3745d2519 | ||
![]() |
931f3fa41a | ||
![]() |
87b5a91212 | ||
![]() |
3b8da62d84 | ||
![]() |
86a48294f4 | ||
![]() |
a03884981f | ||
![]() |
ab695f90c7 | ||
![]() |
efcf8f9555 | ||
![]() |
f71903a563 | ||
![]() |
95552e9a5b | ||
![]() |
5da57271b2 | ||
![]() |
62a7139f4d | ||
![]() |
a7be26cd95 | ||
![]() |
9c3b0952e0 | ||
![]() |
c771f446b4 | ||
![]() |
9a25561017 | ||
![]() |
bd870f0537 | ||
![]() |
d7f43bddfa | ||
![]() |
87107c5a59 | ||
![]() |
9ce920b35a | ||
![]() |
15aff9662c | ||
![]() |
da6fb91886 | ||
![]() |
1e880f7406 | ||
![]() |
81153042d3 | ||
![]() |
493ca261dc | ||
![]() |
7493b340ca | ||
![]() |
e85e60ed6a | ||
![]() |
8ff4d5dcbf | ||
![]() |
f2838e493b | ||
![]() |
a71edcf1a1 | ||
![]() |
47bef74e7c | ||
![]() |
b757a7e3fe | ||
![]() |
362ff5724d | ||
![]() |
4f8363a5c2 | ||
![]() |
ae3925118c | ||
![]() |
b2fcab20a6 | ||
![]() |
6423957d29 | ||
![]() |
835cdad0a9 | ||
![]() |
d8d6decb38 | ||
![]() |
16b42cc109 | ||
![]() |
a47f27821f | ||
![]() |
c797e7a973 | ||
![]() |
245eb64405 | ||
![]() |
a895fcf057 | ||
![]() |
5706fb26b8 | ||
![]() |
3f82120cdc | ||
![]() |
20df183470 | ||
![]() |
980216795f | ||
![]() |
fdfcd841ba | ||
![]() |
28a09794e9 | ||
![]() |
a0c9217375 | ||
![]() |
469176c59b | ||
![]() |
3ece672890 | ||
![]() |
c6ebba8843 | ||
![]() |
1f047807a4 | ||
![]() |
f1b724c49a | ||
![]() |
5ebed2046c | ||
![]() |
d1236a53b8 | ||
![]() |
84f07ee992 | ||
![]() |
d7f5e48626 | ||
![]() |
56e07bb1f2 | ||
![]() |
45b2700375 | ||
![]() |
d860b35f41 | ||
![]() |
5392062edd | ||
![]() |
d9a09a2aea | ||
![]() |
3daff73d36 | ||
![]() |
e6b88ec087 | ||
![]() |
592dcec852 | ||
![]() |
2a6b79ec0f | ||
![]() |
afc1d224a0 | ||
![]() |
b668acda24 | ||
![]() |
c3996d6931 | ||
![]() |
9ec174776c | ||
![]() |
6a8722cf7c | ||
![]() |
000b1d80b0 | ||
![]() |
6d8654610e | ||
![]() |
5cd4c8e896 | ||
![]() |
cec8db173b | ||
![]() |
dd9dad80be | ||
![]() |
9992ade051 | ||
![]() |
36da4a9b72 | ||
![]() |
3fc34244ac | ||
![]() |
753c07e911 | ||
![]() |
d0850e2931 | ||
![]() |
c704df004a | ||
![]() |
d95c9c496e | ||
![]() |
d28f4ed618 | ||
![]() |
7a0580eff5 | ||
![]() |
f94af84f2a | ||
![]() |
31fb199670 | ||
![]() |
a1ca0a1cb2 | ||
![]() |
2326c23133 | ||
![]() |
d4c1d1bdb9 | ||
![]() |
8d258871ff | ||
![]() |
49299a6bf0 | ||
![]() |
868b8ad318 | ||
![]() |
40752dcfb6 | ||
![]() |
18f51abfe6 | ||
![]() |
3e2c54dcbd | ||
![]() |
a0cd14b4e8 | ||
![]() |
35c6fdbce8 | ||
![]() |
202addc39d | ||
![]() |
d8cb7c475b | ||
![]() |
03bacd747e | ||
![]() |
97b6a68cda | ||
![]() |
eee18035cf | ||
![]() |
f1b3b0c155 | ||
![]() |
f5d3495c62 | ||
![]() |
e14a356c24 | ||
![]() |
4e7d396e5b | ||
![]() |
34d17ca458 | ||
![]() |
03950f270a | ||
![]() |
7074331461 | ||
![]() |
4c9cd70f65 | ||
![]() |
7a105de969 | ||
![]() |
eec9a28fe8 | ||
![]() |
963f1b1907 | ||
![]() |
dcac9b5f20 | ||
![]() |
765a95c273 | ||
![]() |
6a115d0133 | ||
![]() |
a057effad5 | ||
![]() |
94b0800989 | ||
![]() |
a783b6a0ab | ||
![]() |
5302964eb6 | ||
![]() |
261dbd16a6 | ||
![]() |
672dbc03c6 | ||
![]() |
ed0bdf9e5f | ||
![]() |
735e2e4192 | ||
![]() |
0aabb11220 | ||
![]() |
09ad14bc28 | ||
![]() |
d61e39743b | ||
![]() |
ea90df434b | ||
![]() |
67fc682df2 | ||
![]() |
381b495efc | ||
![]() |
812db815f1 | ||
![]() |
24ee19f1e2 | ||
![]() |
f72c5ebb76 | ||
![]() |
1075ea1220 | ||
![]() |
ce7edca136 | ||
![]() |
3e16857a1e | ||
![]() |
5b1e32f51d | ||
![]() |
4adf5ce826 | ||
![]() |
4a1905a2a2 | ||
![]() |
59af3a396c | ||
![]() |
7c584ece23 | ||
![]() |
ff2c901930 | ||
![]() |
dc8e1773f1 | ||
![]() |
b0d9a2437d | ||
![]() |
2be6ecd50f | ||
![]() |
fa0bb35e6c | ||
![]() |
360bffa3a9 | ||
![]() |
5b503f21d7 | ||
![]() |
2214d9b330 | ||
![]() |
6a2d733d85 | ||
![]() |
7392d5a30a | ||
![]() |
b3deeca939 | ||
![]() |
c38a3a239c | ||
![]() |
afa6ed09ef | ||
![]() |
deb966128f | ||
![]() |
73707fa231 | ||
![]() |
10ac39f6b2 | ||
![]() |
2e05dc8618 | ||
![]() |
d8233b4de5 | ||
![]() |
7cbc3ea65f | ||
![]() |
cb0523660d | ||
![]() |
605bf7e287 | ||
![]() |
3405b2549b | ||
![]() |
d83c617566 | ||
![]() |
7016c19b2f | ||
![]() |
5cd4a0ced6 | ||
![]() |
347c1a2141 | ||
![]() |
46eae64ef6 | ||
![]() |
a74fe60b91 | ||
![]() |
fab70a80bb | ||
![]() |
2abe2f7d59 | ||
![]() |
cc970354d7 | ||
![]() |
e389ff2537 | ||
![]() |
088f0c82bd | ||
![]() |
fa1bb27dd2 | ||
![]() |
5a6ce34352 | ||
![]() |
fdcb88977a | ||
![]() |
a584ccb8f7 | ||
![]() |
cc290b15f6 | ||
![]() |
575db4665d | ||
![]() |
a61aff8432 | ||
![]() |
3aa1c60fe3 | ||
![]() |
39f3aa7e78 | ||
![]() |
01e2c3272b | ||
![]() |
5afcd3e54e | ||
![]() |
b081064954 | ||
![]() |
11e63ca96a | ||
![]() |
6457d46107 | ||
![]() |
987bf4d850 | ||
![]() |
fa80c0a88d | ||
![]() |
f69484ba02 | ||
![]() |
11f63c7868 | ||
![]() |
3245124553 | ||
![]() |
44475967eb | ||
![]() |
2d27b5ac53 | ||
![]() |
2ae161d8b5 | ||
![]() |
aefe83b1a3 | ||
![]() |
f86e85b931 | ||
![]() |
993ebc9eba | ||
![]() |
1d99bbf22e | ||
![]() |
eb4fa635bf | ||
![]() |
49522d93df | ||
![]() |
9e0a7122f5 | ||
![]() |
e4fe7ba985 | ||
![]() |
f3ea11bbc1 | ||
![]() |
55de91530d | ||
![]() |
290bbcfa3e | ||
![]() |
061a1be2bc | ||
![]() |
4bd8c319dd | ||
![]() |
367022dd8c | ||
![]() |
f1975d9dbf | ||
![]() |
0764cf1165 | ||
![]() |
a55a6e5c48 | ||
![]() |
5230aa8917 | ||
![]() |
f7e3e207b7 | ||
![]() |
6f0c59f1be | ||
![]() |
5fcdbd7742 | ||
![]() |
4173ff5339 | ||
![]() |
e8c4d08b25 | ||
![]() |
36081c69e0 | ||
![]() |
65db3c1164 | ||
![]() |
11f02e48d7 | ||
![]() |
e41283a40a | ||
![]() |
738e39413d | ||
![]() |
8df0a950f7 | ||
![]() |
199a274c80 | ||
![]() |
731d1ab796 | ||
![]() |
f6d8868eb6 | ||
![]() |
253cc377b4 | ||
![]() |
1bfd585f3c | ||
![]() |
839eb0fe14 | ||
![]() |
3dcd06806d | ||
![]() |
3c174ce329 | ||
![]() |
8a2347539c | ||
![]() |
f22eca3d9e | ||
![]() |
3cb301214f | ||
![]() |
8215faea0d | ||
![]() |
beab4e0d7c | ||
![]() |
b785d5297a | ||
![]() |
d86d7b8843 | ||
![]() |
2ca5f05555 | ||
![]() |
e95188059f | ||
![]() |
73e6c8541c | ||
![]() |
6f9c8b2aa0 | ||
![]() |
2d20df37b1 | ||
![]() |
0b2e5cd253 | ||
![]() |
0208188bb5 | ||
![]() |
44ae596929 | ||
![]() |
db0cbf1ea9 | ||
![]() |
896da4abbd | ||
![]() |
731e9bbbfc | ||
![]() |
bf1c138a3c | ||
![]() |
00fc3e2c29 | ||
![]() |
a258aa50a5 | ||
![]() |
3cf12a4792 | ||
![]() |
e56f6fafdc | ||
![]() |
e9789e0b3e | ||
![]() |
8aa30b0ccb | ||
![]() |
871a7c87bf | ||
![]() |
72337e4c77 | ||
![]() |
aedd60e74f | ||
![]() |
9249ea0dbb | ||
![]() |
357ec7034e | ||
![]() |
fa4e0519fa | ||
![]() |
ccd1a08aca | ||
![]() |
c654936a91 | ||
![]() |
6c7317fbc3 | ||
![]() |
159e55296f | ||
![]() |
8a084599d8 | ||
![]() |
24b51e0582 | ||
![]() |
2e2faeb612 | ||
![]() |
d3a8af9ed0 | ||
![]() |
def11f9959 | ||
![]() |
0b64151ae0 | ||
![]() |
a86c6e0809 | ||
![]() |
88821b1d0e | ||
![]() |
73f636c40d | ||
![]() |
042e11b1d7 | ||
![]() |
06cc505956 | ||
![]() |
8aab7d5504 | ||
![]() |
c52f73269e | ||
![]() |
fa2ad54d90 | ||
![]() |
fa9af6a021 | ||
![]() |
a3605921c9 | ||
![]() |
e9269a1d33 | ||
![]() |
39807abc7d | ||
![]() |
08ae05cc76 | ||
![]() |
2188603a49 | ||
![]() |
fbe2370df7 | ||
![]() |
8fb1c6535d | ||
![]() |
6534dff4bc | ||
![]() |
44f2897919 | ||
![]() |
30b7e36f10 | ||
![]() |
2f6ad8ea4a | ||
![]() |
78afd566ec | ||
![]() |
02cc679692 | ||
![]() |
1064588c00 | ||
![]() |
54050f10b7 | ||
![]() |
f0cf620854 | ||
![]() |
80f34620c8 | ||
![]() |
7030000348 | ||
![]() |
fc7f1ab42f | ||
![]() |
89a6bc4354 | ||
![]() |
849121a124 | ||
![]() |
ba6ce28d3c | ||
![]() |
4b8447bc82 | ||
![]() |
6698b3a1dc | ||
![]() |
352ef0d009 | ||
![]() |
bb73ecc1f4 | ||
![]() |
7ea8827e69 | ||
![]() |
8fa48a9781 | ||
![]() |
694c768666 | ||
![]() |
51eb4770a7 | ||
![]() |
274a507bc4 | ||
![]() |
da8339066b | ||
![]() |
ee3ee5b165 | ||
![]() |
e86bffdf89 | ||
![]() |
931161b007 | ||
![]() |
2d30ae2bd9 | ||
![]() |
1ad60881cb | ||
![]() |
18cd389c77 | ||
![]() |
26ea97cb44 | ||
![]() |
d7f6db5efd | ||
![]() |
99b25efb67 | ||
![]() |
8699e69ae5 | ||
![]() |
f928818bf1 | ||
![]() |
35e26629af | ||
![]() |
6e7f49591f | ||
![]() |
7fea432102 | ||
![]() |
b76cddcf9f | ||
![]() |
9b2faf207d | ||
![]() |
9e59f07401 | ||
![]() |
928faeba0d | ||
![]() |
521a44b953 | ||
![]() |
fb60479578 | ||
![]() |
29b67505a7 | ||
![]() |
6f178a8a23 | ||
![]() |
b29c295adc | ||
![]() |
8b0f9d4317 | ||
![]() |
e1ba2a8ca2 | ||
![]() |
eb852cec43 | ||
![]() |
0bed5727cb | ||
![]() |
a749ecceed | ||
![]() |
b5b934b8a1 | ||
![]() |
6b09fe2377 | ||
![]() |
cbb4ff2fd9 | ||
![]() |
eb642e8a06 | ||
![]() |
b97d8e163d | ||
![]() |
7c0d2832cd | ||
![]() |
205cfae1a4 | ||
![]() |
f861a2b72c | ||
![]() |
5843e63878 | ||
![]() |
961f8afe53 | ||
![]() |
37769b94cd | ||
![]() |
f5c0c207ec | ||
![]() |
b4344a8de2 | ||
![]() |
e02c200775 | ||
![]() |
8f4435019b | ||
![]() |
ec55f716e1 | ||
![]() |
012f6b660c | ||
![]() |
9de136789c | ||
![]() |
84a8c1312f | ||
![]() |
21f9ad3994 | ||
![]() |
a9e77dc0db | ||
![]() |
626eb77060 | ||
![]() |
7674f6b5aa | ||
![]() |
16c72c491d | ||
![]() |
99e1245c9b | ||
![]() |
f42b137c1b | ||
![]() |
4e852911aa | ||
![]() |
30ab068bfe | ||
![]() |
6499ad6cdb | ||
![]() |
6f99b1d69b | ||
![]() |
42c4ed85a1 | ||
![]() |
3e3697dc7a | ||
![]() |
ff1ab1da37 | ||
![]() |
3da77726d0 | ||
![]() |
879cdcc0a4 | ||
![]() |
9b8a35dbb3 | ||
![]() |
7c7f18b501 | ||
![]() |
6f0a9910ea | ||
![]() |
6e8c971038 | ||
![]() |
09131d8647 | ||
![]() |
930fa18224 | ||
![]() |
9c9c115d1a | ||
![]() |
b6e9643586 | ||
![]() |
83f2acddf8 | ||
![]() |
35f9cc55f1 | ||
![]() |
4483025856 | ||
![]() |
61e4be4456 | ||
![]() |
f11f4510a2 | ||
![]() |
b3c3be0483 | ||
![]() |
7c3df46570 | ||
![]() |
b8793760a1 | ||
![]() |
6264f9c67b | ||
![]() |
2a74deb84e | ||
![]() |
9d1ff37a79 | ||
![]() |
2f99164781 | ||
![]() |
80ef32f09d | ||
![]() |
63be0e2e1a | ||
![]() |
74c4553bb0 | ||
![]() |
e240707b32 | ||
![]() |
7c867852a9 | ||
![]() |
2d149dc746 | ||
![]() |
7edcddd3e4 | ||
![]() |
71f658b560 | ||
![]() |
9886db5d6d | ||
![]() |
c236cd070c | ||
![]() |
9f1a830d32 | ||
![]() |
1e69ce9111 | ||
![]() |
389297155d | ||
![]() |
c341b86520 | ||
![]() |
88eef379b2 | ||
![]() |
34767d4058 | ||
![]() |
12c3d54a63 | ||
![]() |
33a185dade | ||
![]() |
c1c5776d85 | ||
![]() |
eda642554d | ||
![]() |
51f5ce013f | ||
![]() |
44450f9d7d | ||
![]() |
c34e280fc2 | ||
![]() |
aef266b940 | ||
![]() |
f873219d25 | ||
![]() |
7de5646d6b | ||
![]() |
1e89f3ec9a | ||
![]() |
d8d9449e2b | ||
![]() |
850d9a0254 | ||
![]() |
c422bcf1e2 | ||
![]() |
27b7fb6f91 | ||
![]() |
302dbc424b | ||
![]() |
a7922690c4 | ||
![]() |
5541de2bcb | ||
![]() |
4c14184111 | ||
![]() |
d78f63e4d0 | ||
![]() |
f38d50b928 | ||
![]() |
5c5b832d01 | ||
![]() |
2b4c5178f4 | ||
![]() |
59588f960d | ||
![]() |
d3b335f53f | ||
![]() |
c3037bae39 | ||
![]() |
9b1ab34352 | ||
![]() |
221a8597da | ||
![]() |
45022752a0 | ||
![]() |
aa342eb476 | ||
![]() |
32b26b8270 | ||
![]() |
e07c29caad | ||
![]() |
b487c12ab1 | ||
![]() |
60293648dc | ||
![]() |
e7994b3da1 | ||
![]() |
b88bf74e13 | ||
![]() |
8355727eb1 | ||
![]() |
c7290908cc | ||
![]() |
1307cd4b10 | ||
![]() |
c0b2193718 | ||
![]() |
f9bb7e404e | ||
![]() |
bbb8a1bacc | ||
![]() |
0aaa4fa79b | ||
![]() |
4ed81fb03f | ||
![]() |
7d13c2d854 | ||
![]() |
cadbb623c7 | ||
![]() |
4d959fb91c | ||
![]() |
1fb3d8d601 | ||
![]() |
dd4334e3ba | ||
![]() |
5eee47d1e4 | ||
![]() |
54def1ae0e | ||
![]() |
6a36fc75cf | ||
![]() |
bf69d4e0a8 | ||
![]() |
fe248a2ebd | ||
![]() |
49ad9a8bd5 | ||
![]() |
fa75b477e9 | ||
![]() |
3c1d93f503 | ||
![]() |
21fabd3afa | ||
![]() |
0ec4652b52 | ||
![]() |
e901dc4ec4 | ||
![]() |
9d02436a72 | ||
![]() |
9fb7542a6f | ||
![]() |
ddf37a847d | ||
![]() |
024ec2b153 | ||
![]() |
f8b56c460e | ||
![]() |
42277955fa | ||
![]() |
950c332e36 | ||
![]() |
44d6f0bc2b | ||
![]() |
9bff88ad3e | ||
![]() |
8de23b9559 | ||
![]() |
fbba0d9a21 | ||
![]() |
5beb415ada | ||
![]() |
187024367a | ||
![]() |
e6262de5ab | ||
![]() |
c96bb45940 | ||
![]() |
50796a6a77 | ||
![]() |
0fb0e132b6 | ||
![]() |
494a991d10 | ||
![]() |
c32654db18 | ||
![]() |
f4e7ccfcfc | ||
![]() |
4a4cbe011a | ||
![]() |
f68111c59f | ||
![]() |
1d845623a8 | ||
![]() |
a93121a88d | ||
![]() |
4ea1d88826 | ||
![]() |
a87b6fee89 | ||
![]() |
9baf5ad404 | ||
![]() |
5fd7306446 | ||
![]() |
3a8828325a | ||
![]() |
57bf59f6bd | ||
![]() |
f0d81d077f | ||
![]() |
bb5aefb9e4 | ||
![]() |
6a1739e883 | ||
![]() |
ae306893ff | ||
![]() |
5fd17d092b | ||
![]() |
fad1d7bd1f | ||
![]() |
dcf7520d2a | ||
![]() |
998b33c207 | ||
![]() |
7b3e7b7aea | ||
![]() |
09a86d2ed2 | ||
![]() |
285f7ec696 | ||
![]() |
595508bf7d | ||
![]() |
2074c7fcee | ||
![]() |
759d8a3f90 | ||
![]() |
f2fa583101 | ||
![]() |
18feb4bb81 | ||
![]() |
b49a60fa3e | ||
![]() |
fa81a83893 | ||
![]() |
942bf2ef78 | ||
![]() |
a9d4b1afe4 | ||
![]() |
cdd8ba78e7 | ||
![]() |
254d4c6534 | ||
![]() |
33a0db3935 | ||
![]() |
514f83cc96 | ||
![]() |
4950bda406 | ||
![]() |
9e9be6055d | ||
![]() |
c9ccc79789 | ||
![]() |
3fab596518 | ||
![]() |
3378b8d7ce | ||
![]() |
881079ccc1 | ||
![]() |
cf1cbc6d75 | ||
![]() |
e418491f19 | ||
![]() |
a4f75ca249 | ||
![]() |
6ba2d0be31 | ||
![]() |
fb2a671e86 | ||
![]() |
8cb62341ef | ||
![]() |
074378bef6 | ||
![]() |
a772832917 | ||
![]() |
cf467b8593 | ||
![]() |
b4a3470cb9 | ||
![]() |
e44d86479e | ||
![]() |
42345d9a06 | ||
![]() |
40fd7cf852 | ||
![]() |
870350b961 | ||
![]() |
198a6b2e8f | ||
![]() |
0479fc6f54 | ||
![]() |
be6e1e5e15 | ||
![]() |
9ce44845fe | ||
![]() |
1463f05d46 | ||
![]() |
49a9923b5c | ||
![]() |
23844c0f1a | ||
![]() |
82efa0893f | ||
![]() |
9e93d1fd7e | ||
![]() |
0a424f53b1 | ||
![]() |
efc44d83bb | ||
![]() |
9b274a0bc4 | ||
![]() |
aeca2842fe | ||
![]() |
d44d07ffcf | ||
![]() |
bc683ce6ee | ||
![]() |
c6abe1d1bb | ||
![]() |
6a95abb831 | ||
![]() |
6d74a6aa19 | ||
![]() |
83c3275054 | ||
![]() |
b3eb0301ae | ||
![]() |
f00dfd32d4 | ||
![]() |
8ec436423f | ||
![]() |
5f2ae37ee5 | ||
![]() |
514363f1c5 | ||
![]() |
f84f6aa713 | ||
![]() |
b5f15b6d67 | ||
![]() |
c8972a2234 | ||
![]() |
1892c8fa62 | ||
![]() |
589633bc23 | ||
![]() |
458162c3f5 | ||
![]() |
583eb1a80e | ||
![]() |
1480b77461 | ||
![]() |
908a7c6991 | ||
![]() |
53b991fb54 | ||
![]() |
35187a4b52 | ||
![]() |
621326f4e4 | ||
![]() |
a6643d8fb3 | ||
![]() |
9239ace1c8 | ||
![]() |
422bcecec1 | ||
![]() |
6f02550ac3 | ||
![]() |
1aa996d5f0 | ||
![]() |
a340646e1e | ||
![]() |
db043b26da | ||
![]() |
8767599ad4 | ||
![]() |
6d5c000e1f | ||
![]() |
2750535928 | ||
![]() |
1e31e2944b | ||
![]() |
8bcc4f4c82 | ||
![]() |
bfc3080292 | ||
![]() |
9bff86e7aa | ||
![]() |
0689a6ed62 | ||
![]() |
61f2251336 | ||
![]() |
62a0932deb | ||
![]() |
3389ee4b80 | ||
![]() |
cc6e2ef3f7 | ||
![]() |
1a1c95af12 | ||
![]() |
658299ee21 | ||
![]() |
5b8ca8d0ed | ||
![]() |
d91528648f | ||
![]() |
8b88272bc0 | ||
![]() |
8ab59bee47 | ||
![]() |
18c814d3dc | ||
![]() |
4f0928d93b | ||
![]() |
6c7865a247 | ||
![]() |
7cf63d1985 | ||
![]() |
0b02b43b11 | ||
![]() |
b25a0e2272 | ||
![]() |
6d78c961d9 | ||
![]() |
e370248c9e | ||
![]() |
31c2d22912 | ||
![]() |
5eb25b2d4a | ||
![]() |
6737c51fca | ||
![]() |
d23c9f715e | ||
![]() |
03ccb529e4 | ||
![]() |
505e09242d | ||
![]() |
d6b4f1c95d | ||
![]() |
67c0af4c57 | ||
![]() |
cba0cf0609 | ||
![]() |
b957017799 | ||
![]() |
ebe71a1a38 | ||
![]() |
6feb9d4b4e | ||
![]() |
06d6155862 | ||
![]() |
f13bdd0da4 | ||
![]() |
3489ea30dd | ||
![]() |
d218ac85f7 | ||
![]() |
5129c7521b | ||
![]() |
eb19c7af32 | ||
![]() |
6b65b21ee0 | ||
![]() |
f7794ea6b5 | ||
![]() |
4eda081574 | ||
![]() |
234c4c1958 | ||
![]() |
ad3c4d24b8 | ||
![]() |
ee37b32ca1 | ||
![]() |
49721a541a | ||
![]() |
c18d96e2f5 | ||
![]() |
3efb009e82 | ||
![]() |
7a1bea7ff5 | ||
![]() |
c7c645776d | ||
![]() |
b20f46e8b9 | ||
![]() |
4f0ece1bb4 | ||
![]() |
2c316c5820 | ||
![]() |
9d10d8f55e | ||
![]() |
0fcac987df | ||
![]() |
ffcc2254ce | ||
![]() |
b01eac3ba5 | ||
![]() |
a3341c4330 | ||
![]() |
667cb772e9 | ||
![]() |
933d008e52 | ||
![]() |
a4fac730d4 | ||
![]() |
d868f39aea | ||
![]() |
28d776a0b0 | ||
![]() |
b5d541b596 | ||
![]() |
4948499889 | ||
![]() |
7696b101f6 | ||
![]() |
fd2987a9fd | ||
![]() |
4c1d32020a | ||
![]() |
b40bdab0ae | ||
![]() |
d192aecd3b | ||
![]() |
d1781f5766 | ||
![]() |
2c4461457a | ||
![]() |
82959081de | ||
![]() |
acdac6d5e8 | ||
![]() |
d3d7889883 | ||
![]() |
60ece3e1c9 | ||
![]() |
a9f8529460 | ||
![]() |
ec53b61f9e | ||
![]() |
e9f02edd8b | ||
![]() |
d1b7898219 | ||
![]() |
8dc21ef619 | ||
![]() |
d9f91598a5 | ||
![]() |
c540acf2bd | ||
![]() |
f702f3efcd | ||
![]() |
9410061405 | ||
![]() |
485b28d9ea | ||
![]() |
d59200a9f5 | ||
![]() |
44a92ca81c | ||
![]() |
d39fa39a03 | ||
![]() |
36ec857523 | ||
![]() |
fcb8cdc146 | ||
![]() |
2322b0b65f | ||
![]() |
87baaf4255 | ||
![]() |
b7f0e877f0 | ||
![]() |
5d92a04732 | ||
![]() |
8ff879df22 | ||
![]() |
9fb7ee676e | ||
![]() |
2c855a3986 | ||
![]() |
cdd4894e30 | ||
![]() |
5f26226712 | ||
![]() |
8baf61031d | ||
![]() |
e90ba40553 | ||
![]() |
b38016425f | ||
![]() |
ee5e3f7691 | ||
![]() |
ca07975ead | ||
![]() |
5a1a41beb1 | ||
![]() |
20a3a061a1 | ||
![]() |
7b78f6db17 | ||
![]() |
5816a24577 | ||
![]() |
0e4f44b775 | ||
![]() |
0105332476 | ||
![]() |
4aca9cd66b | ||
![]() |
a4234bf80e | ||
![]() |
f42f698dbc | ||
![]() |
cd45c5d886 | ||
![]() |
2af6ee7584 | ||
![]() |
3b437c9b84 | ||
![]() |
af8ecdd48d | ||
![]() |
16d9ccd423 | ||
![]() |
e1d223f726 | ||
![]() |
a4904a3f2d | ||
![]() |
dff7b30405 | ||
![]() |
a06cd770a4 | ||
![]() |
56c4121eb2 | ||
![]() |
f519b20495 | ||
![]() |
32da8c52f7 | ||
![]() |
c6994731b1 | ||
![]() |
2eb1041f4b | ||
![]() |
4ad5eb5a82 | ||
![]() |
ea38639395 | ||
![]() |
bf0d2e9bd2 | ||
![]() |
bb3c2175bc | ||
![]() |
6fafafbed0 | ||
![]() |
8f73c53d26 | ||
![]() |
5a09847596 | ||
![]() |
cf63175232 | ||
![]() |
88428fc772 | ||
![]() |
505dfcbcd9 | ||
![]() |
7cbcb21e80 | ||
![]() |
d4dbd76a0a | ||
![]() |
1d9343df7f | ||
![]() |
c7ca88e666 | ||
![]() |
efbb94a1b1 | ||
![]() |
eee6e8a2c3 | ||
![]() |
a26cdef427 | ||
![]() |
a5013cddd5 | ||
![]() |
844515787b | ||
![]() |
d5476a1da1 | ||
![]() |
12ae70630f | ||
![]() |
954a47d9ef | ||
![]() |
6ed847f49e | ||
![]() |
ea50bbeb11 | ||
![]() |
aefadd6684 | ||
![]() |
4096a8931a | ||
![]() |
d2bd0e8ca2 | ||
![]() |
e119675100 | ||
![]() |
5ff2608794 | ||
![]() |
96d1c9ab91 | ||
![]() |
60268e97d4 | ||
![]() |
b51bb668c6 | ||
![]() |
87e5b024c1 | ||
![]() |
54f3bb8ddf | ||
![]() |
fa291c20e5 | ||
![]() |
dd97d5bc7e | ||
![]() |
b3fccc0de6 | ||
![]() |
9fe306f056 | ||
![]() |
1b66278a68 | ||
![]() |
76015740f8 | ||
![]() |
816edb66c7 | ||
![]() |
82c688e3be | ||
![]() |
46d6241f58 | ||
![]() |
b5083ce973 | ||
![]() |
1663756983 | ||
![]() |
f344314762 | ||
![]() |
7f4d178781 | ||
![]() |
ba629fbddb | ||
![]() |
157c776019 | ||
![]() |
70aacfce98 | ||
![]() |
8625a36d1d | ||
![]() |
002f5b5ee6 | ||
![]() |
b058b2574f | ||
![]() |
170e6bdcab | ||
![]() |
075a0ad780 | ||
![]() |
e7c2e86c93 | ||
![]() |
3ca1f07cc4 | ||
![]() |
762c752918 | ||
![]() |
06a2de4d1c | ||
![]() |
d4f47bfc6b | ||
![]() |
271a4ba7c8 | ||
![]() |
528ca49368 | ||
![]() |
5d8c90ae0d | ||
![]() |
f872dc8948 | ||
![]() |
ec520b8cf5 | ||
![]() |
ff8b96a19f | ||
![]() |
f6b55c7eb9 | ||
![]() |
3aae280de5 | ||
![]() |
6c1f9e39c4 | ||
![]() |
a957db7c27 | ||
![]() |
3a670e74f7 | ||
![]() |
a114ecfb73 | ||
![]() |
626935ee14 | ||
![]() |
3f2975e93f | ||
![]() |
38bf06e179 | ||
![]() |
12fc458abb | ||
![]() |
0ed7348d2d | ||
![]() |
67e7554702 | ||
![]() |
74141c39ea | ||
![]() |
cb09207cd7 | ||
![]() |
894cc7cc4d | ||
![]() |
36192ebc3a | ||
![]() |
26663756a5 | ||
![]() |
167e766811 | ||
![]() |
08304ca5f3 | ||
![]() |
323c459442 | ||
![]() |
89c9288706 | ||
![]() |
480d645650 | ||
![]() |
553091e95e | ||
![]() |
dacc4c230d | ||
![]() |
cb07e64b47 | ||
![]() |
8dee5851d2 | ||
![]() |
c14380247b | ||
![]() |
c6ac8780ca | ||
![]() |
8f3f8fa35f | ||
![]() |
1cedacc395 | ||
![]() |
19a39a3647 | ||
![]() |
5c2f19de88 | ||
![]() |
4ccd30865b | ||
![]() |
7ad13c8897 | ||
![]() |
4813b5c882 | ||
![]() |
a787c6a31e | ||
![]() |
f2e4bcea19 | ||
![]() |
cd2313d2ca | ||
![]() |
79b984d612 | ||
![]() |
bf003d643c | ||
![]() |
04fa699498 | ||
![]() |
4020c987b5 | ||
![]() |
2818f74634 | ||
![]() |
a026820483 | ||
![]() |
33fa8df73e | ||
![]() |
2ed70ef241 | ||
![]() |
04dfa45db0 | ||
![]() |
8d82ef8e36 | ||
![]() |
43f93c74da | ||
![]() |
1e104ba40b | ||
![]() |
056d26f13c | ||
![]() |
3e4a077862 | ||
![]() |
a44adf2e6f | ||
![]() |
7c488f1e54 | ||
![]() |
bea389eed7 | ||
![]() |
e96f2f06fb | ||
![]() |
9a897d5e12 | ||
![]() |
b35a44a0e0 | ||
![]() |
8aee79085a | ||
![]() |
d7ca168b77 | ||
![]() |
638b88c61c | ||
![]() |
62845fe4a7 | ||
![]() |
c93b4cf61a | ||
![]() |
55de21477c | ||
![]() |
dcef86a30d | ||
![]() |
0a7b4d18dc | ||
![]() |
bd8c723e08 | ||
![]() |
cd7d7cd35c | ||
![]() |
33cbebc727 | ||
![]() |
6da37691ff | ||
![]() |
ae0f27c42f | ||
![]() |
660cbc136f | ||
![]() |
52143155e7 | ||
![]() |
a29ba51bdb | ||
![]() |
051a503047 | ||
![]() |
8121d147a6 | ||
![]() |
913d3d4ac6 | ||
![]() |
236f33537b | ||
![]() |
9f4b2ad05a | ||
![]() |
f290199606 | ||
![]() |
9692d637ca | ||
![]() |
904265bca7 | ||
![]() |
1ab8deff3d | ||
![]() |
4ab31e2d4e | ||
![]() |
d7e36513b5 | ||
![]() |
d07378e87b | ||
![]() |
e235a04dae | ||
![]() |
be32968ed4 | ||
![]() |
31c660557d | ||
![]() |
414fe53261 | ||
![]() |
1d10c81ff3 | ||
![]() |
1e55d4b613 | ||
![]() |
f9cd0f37f7 | ||
![]() |
39ebc103df | ||
![]() |
52724c5c22 | ||
![]() |
7af6a4f493 | ||
![]() |
0abe57edaa | ||
![]() |
c25f26a290 | ||
![]() |
8d62cb60a6 | ||
![]() |
8d95fb3b31 | ||
![]() |
69e241d2e6 | ||
![]() |
4f799069ea | ||
![]() |
af708b78e0 | ||
![]() |
f46e659740 | ||
![]() |
7bd517e6ff | ||
![]() |
e9abdab1f5 | ||
![]() |
86eee4f041 | ||
![]() |
9db60c830c | ||
![]() |
c43a4682b9 | ||
![]() |
2a4996055a | ||
![]() |
4643fc2c14 | ||
![]() |
6410b90d82 | ||
![]() |
e5c00eceae | ||
![]() |
fe65579df8 | ||
![]() |
281beecb05 | ||
![]() |
7546b5d269 | ||
![]() |
490e3201b9 | ||
![]() |
04be575139 | ||
![]() |
854cae7f12 | ||
![]() |
109d20978f | ||
![]() |
f8d284ec4b | ||
![]() |
06ebe0810f | ||
![]() |
802ad2ff51 | ||
![]() |
9070a8d579 | ||
![]() |
64e1735647 | ||
![]() |
9c538d1e22 | ||
![]() |
3c60bff7dc | ||
![]() |
61d2c9335f | ||
![]() |
f4ed9edec6 | ||
![]() |
5eea5858ea | ||
![]() |
a05785529f | ||
![]() |
a407a3c98d | ||
![]() |
986095482f | ||
![]() |
5ca0441771 | ||
![]() |
79fe8650f8 | ||
![]() |
93418f587c | ||
![]() |
9ed8419b5d | ||
![]() |
b5721604b9 | ||
![]() |
b7d9ad1c7d | ||
![]() |
5e04347f13 | ||
![]() |
1cc8a170e6 | ||
![]() |
88455702bb | ||
![]() |
471b05ff4b | ||
![]() |
95ffa20bd5 | ||
![]() |
5424fa0a00 | ||
![]() |
7152c86591 | ||
![]() |
b9e17c6cc6 | ||
![]() |
7751964db4 | ||
![]() |
2f180c96c8 | ||
![]() |
74d6019f81 | ||
![]() |
b84096097c | ||
![]() |
b9d819e0e5 | ||
![]() |
30e50d261d | ||
![]() |
3b2ff38f02 | ||
![]() |
3ed4859db9 | ||
![]() |
380fb6176b | ||
![]() |
fefa2a9dd6 | ||
![]() |
53d2347c10 | ||
![]() |
b2af1084f9 | ||
![]() |
cf005feace | ||
![]() |
7a9a4db8d7 | ||
![]() |
934e81db43 | ||
![]() |
8b3a43258d | ||
![]() |
b7bc9607a2 | ||
![]() |
c2eb72fce4 | ||
![]() |
1d694450ef | ||
![]() |
98c56bce4b | ||
![]() |
ec396513a2 | ||
![]() |
0b61b62334 | ||
![]() |
dfa180ba64 | ||
![]() |
db44ed845d | ||
![]() |
4a562b5085 | ||
![]() |
03c70e18df | ||
![]() |
df5cdf7de4 | ||
![]() |
1860db4632 | ||
![]() |
09d25f322a | ||
![]() |
33d895bc7d | ||
![]() |
02ca1f2889 | ||
![]() |
e8335b1ed7 | ||
![]() |
ec96e54f87 | ||
![]() |
519a416837 | ||
![]() |
d56a3ac652 | ||
![]() |
d13beec3e1 | ||
![]() |
5d0de138f6 | ||
![]() |
06edb2e36b | ||
![]() |
23ade8180a | ||
![]() |
314f658d92 | ||
![]() |
48cbe22609 | ||
![]() |
f8a15c8228 | ||
![]() |
17f6ded7b0 | ||
![]() |
691cb378a0 | ||
![]() |
2876e5d0cd | ||
![]() |
2601217209 | ||
![]() |
a7be9e6643 | ||
![]() |
e8b2a3de8b | ||
![]() |
30ea27d4a5 | ||
![]() |
39549d5dd4 | ||
![]() |
2a66c03d73 | ||
![]() |
0c19e47bd4 | ||
![]() |
05507d77e3 | ||
![]() |
6b34c38d21 | ||
![]() |
94558e2d40 | ||
![]() |
4f22fe8f7f | ||
![]() |
9e7dfbb857 | ||
![]() |
02d182239a | ||
![]() |
4e0f581747 | ||
![]() |
42d97d348c | ||
![]() |
69380c85ca | ||
![]() |
b38c647830 | ||
![]() |
2396fd1090 | ||
![]() |
0871bf13a4 | ||
![]() |
4c44d2f4d9 | ||
![]() |
833a8be2d1 | ||
![]() |
f8113ae80b | ||
![]() |
feff5355c8 | ||
![]() |
6fbee5c2e3 | ||
![]() |
8200c234dd | ||
![]() |
dfd86d56ec | ||
![]() |
93162f6b65 | ||
![]() |
93ea88f3de | ||
![]() |
ca48b07858 | ||
![]() |
795e01512a | ||
![]() |
36857b4b20 | ||
![]() |
8432b6a790 | ||
![]() |
aa4eb89eee | ||
![]() |
1b1bc6af95 | ||
![]() |
f17003a79c | ||
![]() |
e02a6f2f19 | ||
![]() |
6b45b0f522 | ||
![]() |
ec70e8b0cd | ||
![]() |
d888c70ff0 | ||
![]() |
f29444002e | ||
![]() |
fc66997a36 | ||
![]() |
35513ae072 | ||
![]() |
c35ec1f12b | ||
![]() |
bb7e1d4723 | ||
![]() |
2305cb0131 | ||
![]() |
253293c986 | ||
![]() |
1040fe50ec | ||
![]() |
6a012498a5 | ||
![]() |
74c2060c49 | ||
![]() |
cd363d48c3 | ||
![]() |
d47ef835d7 | ||
![]() |
00177c699e | ||
![]() |
11b0086a01 | ||
![]() |
ceb177f80e | ||
![]() |
fa3832fbd7 | ||
![]() |
2b9c903429 | ||
![]() |
a7c43f9b49 | ||
![]() |
b428196149 | ||
![]() |
e23da1a90f | ||
![]() |
3951c2ea66 | ||
![]() |
fee152654d | ||
![]() |
51073c948c | ||
![]() |
91438088a0 | ||
![]() |
177fff3ff0 | ||
![]() |
e7fadcda7b | ||
![]() |
91c53e9c52 | ||
![]() |
bd1c66984f | ||
![]() |
704777444c | ||
![]() |
c28a6a867d | ||
![]() |
4bfc96c02b | ||
![]() |
faac51d219 | ||
![]() |
d9cd62bf65 | ||
![]() |
6007629293 | ||
![]() |
426e9846d9 | ||
![]() |
935db1308f | ||
![]() |
597540b611 | ||
![]() |
e0b030c892 | ||
![]() |
da9b3dc68b | ||
![]() |
23b79b2f39 | ||
![]() |
b9a0d553ab | ||
![]() |
c4f0d9d2fa | ||
![]() |
78338f161f | ||
![]() |
aaafdee56f | ||
![]() |
7068986c14 | ||
![]() |
32ee31b8c7 | ||
![]() |
50c12d4487 | ||
![]() |
2427b77363 | ||
![]() |
fa9613a879 | ||
![]() |
145e02769c | ||
![]() |
c151696357 | ||
![]() |
cbcd1929dd | ||
![]() |
7a9836064d | ||
![]() |
3155c1cd4f | ||
![]() |
28c38e92d4 | ||
![]() |
9c3b9eee2a | ||
![]() |
def50b255d | ||
![]() |
aa7694e81c | ||
![]() |
a722912e05 | ||
![]() |
a09213bce8 | ||
![]() |
0abaaa0a06 | ||
![]() |
363b88407c | ||
![]() |
427e1abdae | ||
![]() |
6e7ac45ac0 | ||
![]() |
4b3b9ebc29 | ||
![]() |
4a4458ec5b | ||
![]() |
b3379e1921 | ||
![]() |
09e5fbb258 | ||
![]() |
b758dc202f | ||
![]() |
c5f75bc135 | ||
![]() |
a904df5bc2 | ||
![]() |
649d8638ed | ||
![]() |
12c4152dbe | ||
![]() |
8f9572bb05 | ||
![]() |
6d022ff4e0 | ||
![]() |
c0c2edb90a | ||
![]() |
b014219fdd | ||
![]() |
216b8ef400 | ||
![]() |
f2ccd46267 | ||
![]() |
e16ba27ce8 | ||
![]() |
506526a6a2 | ||
![]() |
a88678cf42 | ||
![]() |
d0b61af7ec | ||
![]() |
1978e94aaa | ||
![]() |
28dbf6e3dc | ||
![]() |
ef989160af | ||
![]() |
4071eb76c7 | ||
![]() |
ac723161c1 | ||
![]() |
94884d33db | ||
![]() |
64994277b1 | ||
![]() |
8abf822d92 | ||
![]() |
6e6f10c085 | ||
![]() |
1c0768dd78 | ||
![]() |
c888502671 | ||
![]() |
58af3545f4 | ||
![]() |
d669dd45cf | ||
![]() |
05a5b8cdf0 | ||
![]() |
33b6d0a45f | ||
![]() |
fba11d8016 | ||
![]() |
314834b4eb | ||
![]() |
46a8325556 | ||
![]() |
86622cd29d | ||
![]() |
c91a1d0fce | ||
![]() |
778a2891ce | ||
![]() |
560c719b0f | ||
![]() |
d5ab86edbf | ||
![]() |
6aeb7f36f6 | ||
![]() |
f6308368b0 | ||
![]() |
c0e8f14745 | ||
![]() |
0488012c77 | ||
![]() |
f247183e11 | ||
![]() |
04f5315ab2 | ||
![]() |
7f9e4ba39e | ||
![]() |
06aaf188ea | ||
![]() |
627f994872 | ||
![]() |
9e81ec5aae | ||
![]() |
69753fca1d | ||
![]() |
7773cc121e | ||
![]() |
3aa56936ad | ||
![]() |
e66416c23d | ||
![]() |
a592feae3d | ||
![]() |
fc0d71e891 | ||
![]() |
d4640f1d24 | ||
![]() |
6fe158836e | ||
![]() |
629c0087f4 | ||
![]() |
363bd75129 | ||
![]() |
7592d350a8 | ||
![]() |
8ac8401b4e | ||
![]() |
eed075dbfa | ||
![]() |
23dbdedfb6 | ||
![]() |
85ad29e28e | ||
![]() |
35fc81b038 | ||
![]() |
5d45b84cd2 | ||
![]() |
c662b94d06 | ||
![]() |
ee4bf165b5 | ||
![]() |
92ac396d19 | ||
![]() |
03366038ce | ||
![]() |
0b91aa9202 | ||
![]() |
ffc4fa1c2a | ||
![]() |
15e03957a9 | ||
![]() |
0be881bca6 | ||
![]() |
e88b321741 | ||
![]() |
0c4cb27fe9 | ||
![]() |
1639163c2e | ||
![]() |
f043404cd9 | ||
![]() |
018651ff1d | ||
![]() |
704d7a037c | ||
![]() |
7336178e03 | ||
![]() |
1c16fb8e42 | ||
![]() |
3ab2cd3fb7 | ||
![]() |
5057343b6a | ||
![]() |
6c3e85fd5e | ||
![]() |
f046456445 | ||
![]() |
e81a08916a | ||
![]() |
85d2e3d006 | ||
![]() |
936b0b32ed | ||
![]() |
0d511c697c | ||
![]() |
5bfe034b4d | ||
![]() |
cf786b3b04 | ||
![]() |
0f9f090db2 | ||
![]() |
302eea7418 | ||
![]() |
b5e1f7e03e | ||
![]() |
02397a8d2d | ||
![]() |
ea9437eab2 | ||
![]() |
aaea30bee0 | ||
![]() |
9c869fa701 | ||
![]() |
5106548f2c | ||
![]() |
506d485c0d | ||
![]() |
da190ec96f | ||
![]() |
9567929484 | ||
![]() |
dc16494332 | ||
![]() |
933f422588 | ||
![]() |
663d0691a7 | ||
![]() |
97a0b9272e | ||
![]() |
3d49000c75 | ||
![]() |
68d1a3c0a2 | ||
![]() |
b06de7a687 | ||
![]() |
963ea6141c | ||
![]() |
7232d36494 | ||
![]() |
12eb071e8a | ||
![]() |
86be626c69 | ||
![]() |
ec20e41836 | ||
![]() |
b3564b6cff | ||
![]() |
acbee815be | ||
![]() |
5a1aeff85c | ||
![]() |
4463e4c42b | ||
![]() |
4103ef71c9 | ||
![]() |
dccaa2dd2d | ||
![]() |
5ac6096e08 | ||
![]() |
4734a82f99 | ||
![]() |
c6c2309dee | ||
![]() |
5e1bbd8bff | ||
![]() |
4761207097 | ||
![]() |
b4a6ca63b3 | ||
![]() |
a5b320180a | ||
![]() |
bcc767136c | ||
![]() |
d3257d96d0 | ||
![]() |
89df6a82b0 | ||
![]() |
e725ba403b | ||
![]() |
578fece13e | ||
![]() |
0eeb6b5fd5 | ||
![]() |
ad3f7f041f | ||
![]() |
dce9bfd359 | ||
![]() |
8f96ccc835 | ||
![]() |
a48dd05035 | ||
![]() |
eb90958341 | ||
![]() |
31ed6a48cb | ||
![]() |
dfa80f0787 | ||
![]() |
73acfa6a8e | ||
![]() |
efad20cdff | ||
![]() |
11d68cef54 | ||
![]() |
9c28e60475 | ||
![]() |
9ee79b87ee | ||
![]() |
600aedc9a1 | ||
![]() |
5b5efb5aaa | ||
![]() |
391b3ed1e7 | ||
![]() |
24277259ad | ||
![]() |
65261de7cc | ||
![]() |
beb92a7f9c | ||
![]() |
f1b059c75d | ||
![]() |
d4970f81aa | ||
![]() |
29219afb7f | ||
![]() |
7fbf15edc9 | ||
![]() |
c8d3fa6768 | ||
![]() |
ea5cf3d854 | ||
![]() |
4a833fb489 | ||
![]() |
02aa823d25 | ||
![]() |
92034aeecc | ||
![]() |
9f2232fad1 | ||
![]() |
2be2d54a5c | ||
![]() |
ed99686cc1 | ||
![]() |
a6c1f1e485 | ||
![]() |
a219445751 | ||
![]() |
b65b5aacb6 | ||
![]() |
bcead72265 | ||
![]() |
35b9564ed4 | ||
![]() |
aba01d4361 | ||
![]() |
4398af51c8 | ||
![]() |
83f4f4cc96 | ||
![]() |
1800e6fb8e | ||
![]() |
7766649304 | ||
![]() |
07e9020dfa | ||
![]() |
f504a759e0 | ||
![]() |
9927de4801 | ||
![]() |
1244fc4682 | ||
![]() |
e77a1b12f7 | ||
![]() |
5459daaa10 | ||
![]() |
400131df78 | ||
![]() |
28e1843ff9 | ||
![]() |
df777318d1 | ||
![]() |
6ad5e9e89c | ||
![]() |
a0bd8deee9 | ||
![]() |
405cbd6a00 | ||
![]() |
3e0eb5ab2c | ||
![]() |
fad75a70b6 | ||
![]() |
d9720283df | ||
![]() |
43b83c855f | ||
![]() |
20e2de200f | ||
![]() |
ed4ebe1222 | ||
![]() |
4e4446cef4 | ||
![]() |
e2ff0b265d | ||
![]() |
6d48fc183a | ||
![]() |
ea8392a4a1 | ||
![]() |
bcd296822d | ||
![]() |
6ee97f341d | ||
![]() |
aa2ab74ee9 | ||
![]() |
49b2ab9889 | ||
![]() |
2549e2cc0f | ||
![]() |
b15fa81a44 | ||
![]() |
09f6246d1b | ||
![]() |
96ff389fd1 | ||
![]() |
b55f1df297 | ||
![]() |
d88f7b8600 | ||
![]() |
df2a94bb5b | ||
![]() |
c4ac492c6e | ||
![]() |
fcd4d3e2df | ||
![]() |
42d6bd3839 | ||
![]() |
d6b48003b6 | ||
![]() |
ba8f69d956 | ||
![]() |
f22bb72d18 | ||
![]() |
f7a0a9fa41 | ||
![]() |
3795d653c5 | ||
![]() |
14eed1778b | ||
![]() |
049aaa7e8b | ||
![]() |
35717e8216 | ||
![]() |
2a081abc18 | ||
![]() |
b7f29c7358 | ||
![]() |
3bb6373df5 | ||
![]() |
e1b4edec50 | ||
![]() |
147bee57e1 | ||
![]() |
fcdaea64da | ||
![]() |
8ee014b855 | ||
![]() |
1ab5bdf85f | ||
![]() |
5283e1a39f | ||
![]() |
17c56208ee | ||
![]() |
8474d9fefe | ||
![]() |
fd9f002e9f | ||
![]() |
26268357a0 | ||
![]() |
82b463b22f | ||
![]() |
7ae397a211 | ||
![]() |
ea4ad681e4 | ||
![]() |
a150f9d5ad | ||
![]() |
afb7fe0d40 | ||
![]() |
8bf42b9d3e | ||
![]() |
ba00707d89 | ||
![]() |
d1512d46be | ||
![]() |
2121b943a3 | ||
![]() |
0be7db6270 | ||
![]() |
2af0282725 | ||
![]() |
ef06d2c06e | ||
![]() |
ff458c8417 | ||
![]() |
cc1fac5776 | ||
![]() |
6cb3430c60 | ||
![]() |
4cea90f773 | ||
![]() |
cc93152ff0 | ||
![]() |
9965f01609 | ||
![]() |
e9c76ce694 | ||
![]() |
58ab7d350d | ||
![]() |
e4d6e20ebd | ||
![]() |
45e273897a | ||
![]() |
d9ec7142d7 | ||
![]() |
e162499267 | ||
![]() |
67f21429e3 | ||
![]() |
a0563f06c9 | ||
![]() |
e7c4fdc8bb | ||
![]() |
c490e350bc | ||
![]() |
e11409ef99 | ||
![]() |
5c8e415a76 | ||
![]() |
e795fb9497 | ||
![]() |
d0afabb85c | ||
![]() |
4da5f6188d | ||
![]() |
e7f8b9ad92 | ||
![]() |
473a28c5f2 | ||
![]() |
d765936be3 | ||
![]() |
0db643d9d1 | ||
![]() |
af29159e2f | ||
![]() |
65c38d8e31 | ||
![]() |
adb7aa237b | ||
![]() |
577f86b83a | ||
![]() |
01169e9184 | ||
![]() |
dde037291a | ||
![]() |
6971a189f9 | ||
![]() |
c860686138 | ||
![]() |
2eb507863f | ||
![]() |
54ee5c6998 | ||
![]() |
63df2474a9 | ||
![]() |
93f12fb7c6 | ||
![]() |
b7a995ac53 | ||
![]() |
267a80e70c | ||
![]() |
078be3b8df | ||
![]() |
7b6c967c3a | ||
![]() |
a405ccd044 | ||
![]() |
f6c55ebf05 | ||
![]() |
6b3b4cce4b | ||
![]() |
8887c979b4 | ||
![]() |
665541409a | ||
![]() |
195919b5fb | ||
![]() |
31479056ed | ||
![]() |
9f0976d94a | ||
![]() |
a049d2b7db | ||
![]() |
6959017d55 | ||
![]() |
4ff5a04a72 | ||
![]() |
ea0c4a7263 | ||
![]() |
9633f03ddc | ||
![]() |
4c0d8ce87c | ||
![]() |
4f3e8e9b94 | ||
![]() |
46c1cbbc9c | ||
![]() |
8d9a4ea278 | ||
![]() |
22c83e2393 | ||
![]() |
c83a75f6f9 | ||
![]() |
d92728e533 | ||
![]() |
799962ef0e | ||
![]() |
631f817f11 | ||
![]() |
52f7bdeb5d | ||
![]() |
51db140aed | ||
![]() |
1ad12d5945 | ||
![]() |
9f5d94046d | ||
![]() |
841c727112 | ||
![]() |
d8c9655bfd | ||
![]() |
942ed89cc4 | ||
![]() |
de1e06c39b | ||
![]() |
abbabc11d2 | ||
![]() |
a1fe6b9cf3 | ||
![]() |
2567181cc2 | ||
![]() |
028e4f6029 | ||
![]() |
b82e1a9bef | ||
![]() |
438f226c31 | ||
![]() |
2f139e3cb1 | ||
![]() |
62be82fd3c | ||
![]() |
f0fd5a639a | ||
![]() |
dea00fac3f | ||
![]() |
c30f17f592 | ||
![]() |
e9e95f45d8 | ||
![]() |
e8aa3e6d34 | ||
![]() |
3646884d79 | ||
![]() |
5d75e96fbf | ||
![]() |
dcf2ec5c37 | ||
![]() |
2431e1ba98 | ||
![]() |
4ead108c15 | ||
![]() |
ec8363fa49 | ||
![]() |
e7ff0a3f8b | ||
![]() |
f4c0eb4189 | ||
![]() |
b1ee5a76e1 | ||
![]() |
6b9e8c301b | ||
![]() |
5747c6b1a8 | ||
![]() |
43a5c7ddc8 | ||
![]() |
d9d74107fe | ||
![]() |
373cca9857 | ||
![]() |
284b3f444d | ||
![]() |
dfb088e524 | ||
![]() |
5546f1d73d | ||
![]() |
13fc871806 | ||
![]() |
0f9fd78656 | ||
![]() |
4f318c0be3 | ||
![]() |
5eb1d0a28e | ||
![]() |
66c03713b7 | ||
![]() |
d51070c99b | ||
![]() |
50d050e63e | ||
![]() |
89bf426163 | ||
![]() |
89c3266c7e | ||
![]() |
cff0a632e8 | ||
![]() |
e04d8557ae | ||
![]() |
ca6286f241 | ||
![]() |
35bcc9d5af | ||
![]() |
25b45ce867 | ||
![]() |
d568209bd5 | ||
![]() |
8a43e8af9e | ||
![]() |
377548e3a1 | ||
![]() |
543c6929e6 | ||
![]() |
42ae572948 | ||
![]() |
c3f8b7e200 | ||
![]() |
3a207e2571 | ||
![]() |
6bfd39f094 | ||
![]() |
002ca9611d | ||
![]() |
46ee3d2b26 | ||
![]() |
eb901bcf3a | ||
![]() |
930b4a2c81 | ||
![]() |
22d1b8e1cd | ||
![]() |
785e5b2c16 | ||
![]() |
2e3853dd7d | ||
![]() |
fe99c39e25 | ||
![]() |
c8ab5bc796 | ||
![]() |
4f3b36c2e1 | ||
![]() |
222d89a84c | ||
![]() |
eb3cb0e0c7 | ||
![]() |
69c8f4fbb6 | ||
![]() |
3bcf1c942c | ||
![]() |
220aaf93c6 | ||
![]() |
febc455bc5 | ||
![]() |
57f65c205e | ||
![]() |
6e56486294 | ||
![]() |
3a1e1684ea | ||
![]() |
9d63a49812 | ||
![]() |
7a4ca6dcdc | ||
![]() |
1622638f10 | ||
![]() |
0de3549e6e | ||
![]() |
63d4efda2e | ||
![]() |
b5910dd7d6 | ||
![]() |
c974285490 | ||
![]() |
8db91623ec | ||
![]() |
3eda5333b0 | ||
![]() |
3aaf859985 | ||
![]() |
dba4c197c8 | ||
![]() |
f842640249 | ||
![]() |
aa493ff97d | ||
![]() |
21d5885ded | ||
![]() |
054b3bb26c | ||
![]() |
77bf977d63 | ||
![]() |
3f68e327f3 | ||
![]() |
82db1ffd12 | ||
![]() |
06f6c86ba5 | ||
![]() |
e3f2f30395 | ||
![]() |
4a6d2c91da | ||
![]() |
d7de8c5f68 | ||
![]() |
7bcba2b639 | ||
![]() |
53990f8fad | ||
![]() |
ed7c864869 | ||
![]() |
74ff40e253 | ||
![]() |
57d02d7a17 | ||
![]() |
043603c9be | ||
![]() |
e10801af80 | ||
![]() |
f4fa4056ac | ||
![]() |
208e8ae451 | ||
![]() |
02f8322ac1 | ||
![]() |
e8158234a9 | ||
![]() |
7848c3cd79 | ||
![]() |
2d8420b656 | ||
![]() |
63a86763b1 | ||
![]() |
b5117eb071 | ||
![]() |
f0c774a4bd | ||
![]() |
8bedf97382 | ||
![]() |
65c05d66c0 | ||
![]() |
1cb4332a3c | ||
![]() |
18dfd3db88 | ||
![]() |
dd914deb47 | ||
![]() |
d954d04d12 | ||
![]() |
e95f2c4282 | ||
![]() |
eb1caeb770 | ||
![]() |
56cc4044e4 | ||
![]() |
2208650fde | ||
![]() |
e2a3bfca9a | ||
![]() |
840613f43d | ||
![]() |
e78a19ae3e | ||
![]() |
07bce8850f | ||
![]() |
25a36c1588 | ||
![]() |
ab709aeb46 | ||
![]() |
f3bcb96b41 | ||
![]() |
56a8c74e87 | ||
![]() |
cec21b5507 | ||
![]() |
3a62095af2 | ||
![]() |
8dd179c9e0 | ||
![]() |
c29ca4c50a | ||
![]() |
013439f7c6 | ||
![]() |
646c97a26c | ||
![]() |
e853df4fb0 | ||
![]() |
4cd4201a31 | ||
![]() |
5db52cd5df | ||
![]() |
10d9e0c684 | ||
![]() |
8b9939c344 | ||
![]() |
746f49884c | ||
![]() |
ae18fa2e30 | ||
![]() |
7319637bd5 | ||
![]() |
c8745cc339 | ||
![]() |
44a02ac7a7 | ||
![]() |
73642da7a4 | ||
![]() |
e1eb031022 | ||
![]() |
db66b4093a | ||
![]() |
ef531cec41 | ||
![]() |
1772348eef | ||
![]() |
0920d7d82d | ||
![]() |
2cbe8a4a14 | ||
![]() |
a2f92b1e28 | ||
![]() |
a2d9eb2a5b | ||
![]() |
e72231037e | ||
![]() |
37aaf149f9 | ||
![]() |
83c21570c8 | ||
![]() |
42566b7378 | ||
![]() |
735f877cf1 | ||
![]() |
8f000f222d | ||
![]() |
05ead4d1f5 | ||
![]() |
3775f15461 | ||
![]() |
20a2fdb660 | ||
![]() |
e49b105724 | ||
![]() |
19bc54c1de | ||
![]() |
3e018f2523 | ||
![]() |
f00fb1d9a3 | ||
![]() |
0ddf3c794b | ||
![]() |
77c210fb87 | ||
![]() |
17efff940a | ||
![]() |
32a16ae0f0 | ||
![]() |
3766040960 | ||
![]() |
a1a808b843 | ||
![]() |
e7eb173e07 | ||
![]() |
615afeb4d5 | ||
![]() |
348ebe1402 | ||
![]() |
d20fc30409 | ||
![]() |
13f306ddbc | ||
![]() |
36d32eaabc | ||
![]() |
4f6daa227a | ||
![]() |
b3e054d5a7 | ||
![]() |
5fd219fc9e | ||
![]() |
4e266fe56e | ||
![]() |
c7e2acb4bf | ||
![]() |
9888385dbe | ||
![]() |
11877a3b12 | ||
![]() |
ee3b31c01f | ||
![]() |
598a75379b | ||
![]() |
f864f71028 | ||
![]() |
204b1e1f24 | ||
![]() |
b2377d6da3 | ||
![]() |
d657809ffe | ||
![]() |
33198cd704 | ||
![]() |
61a76b4064 | ||
![]() |
0e6d72dcc8 | ||
![]() |
c8f839068c | ||
![]() |
3132cba51f | ||
![]() |
6661218220 | ||
![]() |
8904f174d2 | ||
![]() |
4472dc533d | ||
![]() |
9fdb69c558 | ||
![]() |
93561543ff | ||
![]() |
1166c9d927 | ||
![]() |
95cc3e31f5 | ||
![]() |
5f093180ab | ||
![]() |
c3bab1f316 | ||
![]() |
e9cf4a209e | ||
![]() |
cb9692f3fb | ||
![]() |
90623bbaff | ||
![]() |
69a375776a | ||
![]() |
06382f33e0 | ||
![]() |
b3e3d77d7c | ||
![]() |
e192bfb62e | ||
![]() |
358f78c7cd | ||
![]() |
83a0ed4250 | ||
![]() |
fa526552dc | ||
![]() |
e96e95c32d | ||
![]() |
265a2ace90 | ||
![]() |
1ae2cebeb1 | ||
![]() |
75cd32b742 | ||
![]() |
86ff540db9 | ||
![]() |
f4bc1a3545 | ||
![]() |
4e6eecf11b | ||
![]() |
0f60fd8c40 | ||
![]() |
b4fd5339c6 | ||
![]() |
ab9d29bf9d | ||
![]() |
12e001cf2b | ||
![]() |
590c588557 | ||
![]() |
d65392a374 | ||
![]() |
0514de3e16 | ||
![]() |
59190786f9 | ||
![]() |
f4d57e3722 | ||
![]() |
d3b8dbb76c | ||
![]() |
6a7fa3769d | ||
![]() |
af96fedc0f | ||
![]() |
174515d197 | ||
![]() |
693de289a2 | ||
![]() |
b171439098 | ||
![]() |
93010ab5c9 | ||
![]() |
842356877e | ||
![]() |
2883f0a1e8 | ||
![]() |
27f529622c | ||
![]() |
3917b460f4 | ||
![]() |
ec5139eb94 | ||
![]() |
5d16a23d79 | ||
![]() |
56cb54588e | ||
![]() |
e2f7133d00 | ||
![]() |
1f122ea54d | ||
![]() |
ef84fc52af | ||
![]() |
9677b0d254 | ||
![]() |
1d36279e79 | ||
![]() |
c2057d19c0 | ||
![]() |
d23a724f79 | ||
![]() |
28ef0a33ad | ||
![]() |
c451518959 | ||
![]() |
f14b76c54b | ||
![]() |
663a204c04 | ||
![]() |
a6ff5391e5 | ||
![]() |
3df1ebf2fc | ||
![]() |
5f3344cd3d | ||
![]() |
56f553e352 | ||
![]() |
8869236e9c | ||
![]() |
f94b55b608 | ||
![]() |
fdaba003ce | ||
![]() |
703848766a | ||
![]() |
ba8ec22587 | ||
![]() |
6b724603c8 | ||
![]() |
2465d0db7b | ||
![]() |
4758452e92 | ||
![]() |
588d6ad4cf | ||
![]() |
c7d1e5a28c | ||
![]() |
8874fbe9c7 | ||
![]() |
ef2485be3b | ||
![]() |
34504f45a5 | ||
![]() |
798ee60ae5 | ||
![]() |
ca10618dc7 | ||
![]() |
77f8ddd948 | ||
![]() |
5c642ef626 | ||
![]() |
489c486278 | ||
![]() |
d8a5881eaa | ||
![]() |
883ce6842d | ||
![]() |
3a80a2d5b9 | ||
![]() |
65279c94ac | ||
![]() |
87db981712 | ||
![]() |
153ccf86b0 | ||
![]() |
9e86ca2e9e | ||
![]() |
e2e80a850c | ||
![]() |
ddd67a7e58 | ||
![]() |
a9df341abf | ||
![]() |
4e2dfba45f | ||
![]() |
61e30d0e91 | ||
![]() |
6d91bdb02e | ||
![]() |
7f640252a1 | ||
![]() |
f245bbd8dd | ||
![]() |
b47d3076cc | ||
![]() |
99d0449cbe | ||
![]() |
1e0b89c381 | ||
![]() |
c56b087d0c | ||
![]() |
4b4d75063c | ||
![]() |
931ce8951e | ||
![]() |
92c619cdd6 | ||
![]() |
436acaf3d0 | ||
![]() |
ec4de0dcce | ||
![]() |
1b8b348eff | ||
![]() |
765691c84d | ||
![]() |
fc0dbcd600 | ||
![]() |
b2942d61b3 | ||
![]() |
37a048a2ca | ||
![]() |
2453e7e686 | ||
![]() |
4479b7b13d | ||
![]() |
1492c59abe | ||
![]() |
5961a46fc0 | ||
![]() |
b7d300b49f | ||
![]() |
dc146e393c | ||
![]() |
9d9b352631 | ||
![]() |
c08cbf3763 | ||
![]() |
ffd5c003cb | ||
![]() |
34318ab655 | ||
![]() |
6027a26761 | ||
![]() |
2571725eb9 | ||
![]() |
84c6fa256c | ||
![]() |
2f244b2b66 | ||
![]() |
1b7e53fd01 | ||
![]() |
bfabf972a8 | ||
![]() |
c0c997eed8 | ||
![]() |
14b07087dc | ||
![]() |
f54a634563 | ||
![]() |
e98d518b0b | ||
![]() |
121ee27105 | ||
![]() |
5681f4f2ea | ||
![]() |
8a63fa3bb7 | ||
![]() |
983a2f513d | ||
![]() |
aab349e787 | ||
![]() |
21ced23c3c | ||
![]() |
a453e9d4c2 | ||
![]() |
1385bcdb90 | ||
![]() |
3f493dce06 | ||
![]() |
403fe36489 | ||
![]() |
66fd7d9e8a | ||
![]() |
c9ceade10d | ||
![]() |
85b6b3a360 | ||
![]() |
a2102f9b98 | ||
![]() |
28cad1d085 | ||
![]() |
9d8dbfbf3f | ||
![]() |
1382a001e3 | ||
![]() |
88e3dcccda | ||
![]() |
43e24cf833 | ||
![]() |
65aef40a3f | ||
![]() |
276e2e8f59 | ||
![]() |
5f67623214 | ||
![]() |
e78e873892 | ||
![]() |
16d335efc0 | ||
![]() |
74ed0e8011 | ||
![]() |
c1753631b1 | ||
![]() |
f84a46680d | ||
![]() |
a9cbc72ce5 | ||
![]() |
466ec0b596 | ||
![]() |
858f0e6657 | ||
![]() |
4ed2689678 | ||
![]() |
1fafe81d20 | ||
![]() |
3101d9099b | ||
![]() |
2785688f57 | ||
![]() |
bce7fcc3c6 | ||
![]() |
021e4fab8c | ||
![]() |
110500b860 | ||
![]() |
e388d0c344 | ||
![]() |
a83bf4f514 | ||
![]() |
a388863e62 | ||
![]() |
d12b4a1460 | ||
![]() |
87c8234cdc | ||
![]() |
72645dff8b | ||
![]() |
b936739917 | ||
![]() |
a45c8d2820 | ||
![]() |
98f7193986 | ||
![]() |
5d1c8ea537 | ||
![]() |
95014dfdd8 | ||
![]() |
53f1dd8adf | ||
![]() |
a338205b73 | ||
![]() |
a030502489 | ||
![]() |
f9bb250621 | ||
![]() |
eca10ea591 | ||
![]() |
32c6fb8629 | ||
![]() |
4bbd49af53 | ||
![]() |
e48a25e952 | ||
![]() |
70ed120c6e | ||
![]() |
a835c85f59 | ||
![]() |
2a4ed9ace7 | ||
![]() |
212d39ba19 | ||
![]() |
5f84fc3ee5 | ||
![]() |
c9b27cf26e | ||
![]() |
df0125abdd | ||
![]() |
d8a4a97ee0 | ||
![]() |
a20601a1f0 | ||
![]() |
d3c40939f6 | ||
![]() |
56e966a980 | ||
![]() |
827d5256c6 | ||
![]() |
c6d3928ed1 | ||
![]() |
3fb0290fba | ||
![]() |
2674b02bfa | ||
![]() |
32f9c07254 | ||
![]() |
2ec80fd1ca | ||
![]() |
adf3e4fcca | ||
![]() |
03bd8cd251 | ||
![]() |
006dde435e | ||
![]() |
9f68ac575d | ||
![]() |
d9cf2750d5 | ||
![]() |
a600bc5e57 | ||
![]() |
100e4425e4 | ||
![]() |
4344e9d604 | ||
![]() |
2ffec3415c | ||
![]() |
8afd9c0c44 | ||
![]() |
d99df8701c | ||
![]() |
05c61b7ec3 | ||
![]() |
2c9eb288e3 | ||
![]() |
6b9c1e17e0 | ||
![]() |
4a5567806b | ||
![]() |
6211e378c3 | ||
![]() |
6af23d2348 | ||
![]() |
6434befdcd | ||
![]() |
a2f0970dfc | ||
![]() |
334359871d | ||
![]() |
245f0a1958 | ||
![]() |
adc3f542cf | ||
![]() |
3fd17c802c | ||
![]() |
f79aa2f73e | ||
![]() |
f28b9ba961 | ||
![]() |
793e36635b | ||
![]() |
7c6abe17a2 | ||
![]() |
f4fe234279 | ||
![]() |
d37783fb21 | ||
![]() |
caf81eecd3 | ||
![]() |
254622878a | ||
![]() |
c41d5f2577 | ||
![]() |
3ce9d47d7d | ||
![]() |
07302ea178 | ||
![]() |
2d82a12e0a | ||
![]() |
f8ab4d0238 | ||
![]() |
11e02f89cf | ||
![]() |
4564d2537b | ||
![]() |
e2460a4393 | ||
![]() |
a170e32852 | ||
![]() |
4176776d70 | ||
![]() |
1cae866da9 | ||
![]() |
de1823070f | ||
![]() |
29f03f5b87 | ||
![]() |
516aaa741d | ||
![]() |
12f5bd2aea | ||
![]() |
30c19ec373 | ||
![]() |
5438532780 | ||
![]() |
fdd36e457d | ||
![]() |
603557af73 | ||
![]() |
36d42760a4 | ||
![]() |
ea259ffa66 | ||
![]() |
52054d69c7 | ||
![]() |
74992344d5 | ||
![]() |
776495dfa2 | ||
![]() |
426be3c11b | ||
![]() |
a93ab74e40 | ||
![]() |
0eac679a5a | ||
![]() |
73a24bf799 | ||
![]() |
412705302d | ||
![]() |
c9276aedde | ||
![]() |
eafea6070d | ||
![]() |
539a28dcba | ||
![]() |
52d86ede3e | ||
![]() |
4dfb56a2f7 | ||
![]() |
290dab25bf | ||
![]() |
e16f0e9af3 | ||
![]() |
a2fec8c2ce | ||
![]() |
9a0837593a | ||
![]() |
f4787d469a | ||
![]() |
a252c19e7c | ||
![]() |
9b57a831f7 | ||
![]() |
18bd8b561a | ||
![]() |
76aef5be9f | ||
![]() |
fb2b3ce7d2 | ||
![]() |
0d1c79b427 | ||
![]() |
a5913147e7 | ||
![]() |
110e827ede | ||
![]() |
74ce703755 | ||
![]() |
4baf72d80b | ||
![]() |
ab6c5af374 | ||
![]() |
5fb03114b5 | ||
![]() |
bddec1168b | ||
![]() |
f19a5b28f7 | ||
![]() |
a9949aece0 | ||
![]() |
15e983e997 | ||
![]() |
a40bb2790e | ||
![]() |
1b91240d54 | ||
![]() |
bbe2a95b3d | ||
![]() |
5351fe3f9b | ||
![]() |
b0db7b432e | ||
![]() |
784381a25f | ||
![]() |
42f0e70cde | ||
![]() |
1ee4f02e70 | ||
![]() |
d061f4ee05 | ||
![]() |
56fe4319a0 | ||
![]() |
a7b6bcf1d6 | ||
![]() |
46973f0446 | ||
![]() |
735c98cb86 | ||
![]() |
b5fa3e74c0 | ||
![]() |
8a552aef9d | ||
![]() |
2ece7fbc11 | ||
![]() |
2424d1c615 | ||
![]() |
bb7b5b9ccb | ||
![]() |
214d14b06b | ||
![]() |
056616f9c5 | ||
![]() |
012b4645f3 | ||
![]() |
4ca31da0a5 | ||
![]() |
d560083e15 | ||
![]() |
d365092bcc | ||
![]() |
4e0985e1a7 | ||
![]() |
011a076155 | ||
![]() |
5f8564bfc5 | ||
![]() |
6b6470f345 | ||
![]() |
7b9ea63f17 | ||
![]() |
3a6ddcf428 | ||
![]() |
4050c216ed | ||
![]() |
91e0f1cb46 | ||
![]() |
675b684290 | ||
![]() |
d69bcc02b0 | ||
![]() |
76244e0d6b | ||
![]() |
ed0b1f58dc | ||
![]() |
16556fa2a9 | ||
![]() |
43898d7845 | ||
![]() |
bff73ee5f8 | ||
![]() |
02a75edf1d | ||
![]() |
c1c8deed0c | ||
![]() |
2fd91e7f9c | ||
![]() |
58ff593f96 | ||
![]() |
89e7536731 | ||
![]() |
b7e2e041bc | ||
![]() |
940625505f | ||
![]() |
f801cfee7e | ||
![]() |
99f661538d | ||
![]() |
13b6cfa438 | ||
![]() |
5dc1a321dd | ||
![]() |
07e7672b78 | ||
![]() |
baafcf48dc | ||
![]() |
ed2ef04b98 | ||
![]() |
c54a2e7338 | ||
![]() |
fa836118b2 | ||
![]() |
11e15b1405 | ||
![]() |
537302ce56 | ||
![]() |
b07c28126a | ||
![]() |
59cab7cd58 | ||
![]() |
8964af428a | ||
![]() |
160b98bd28 | ||
![]() |
2951eb5cc8 | ||
![]() |
588366a514 | ||
![]() |
78a04776e4 | ||
![]() |
324f208d68 | ||
![]() |
e740e341c8 | ||
![]() |
532c860bf0 | ||
![]() |
251bb30dc7 | ||
![]() |
de0efd61d1 | ||
![]() |
e9c8b3acfc | ||
![]() |
08fc6dcff6 | ||
![]() |
96a6d88dca | ||
![]() |
1bd8ff884e | ||
![]() |
a8f1df3e55 | ||
![]() |
7ff842fc37 | ||
![]() |
220bd5a27f | ||
![]() |
4e759e59a4 | ||
![]() |
bd4d0ec4b8 | ||
![]() |
dcc63a6f2e | ||
![]() |
4d1c89f0d1 | ||
![]() |
ae8709be21 | ||
![]() |
5ea7c113b0 | ||
![]() |
831f2dc30e | ||
![]() |
1566ab3b28 | ||
![]() |
c852e1398c | ||
![]() |
761be9342e | ||
![]() |
54ad44a574 | ||
![]() |
fed4015bab | ||
![]() |
019a0ebf9b | ||
![]() |
7607b7d494 | ||
![]() |
8b96a9606d | ||
![]() |
6349821037 | ||
![]() |
db26a42734 | ||
![]() |
74fe35f44e | ||
![]() |
e648716ddf | ||
![]() |
2e20245cdf | ||
![]() |
8726be31ff | ||
![]() |
99b140f73f | ||
![]() |
d952e8186f | ||
![]() |
2b0a2e7644 | ||
![]() |
9820cbb036 | ||
![]() |
f48d94ce34 | ||
![]() |
5daa3167ca | ||
![]() |
23f4f97603 | ||
![]() |
1e8f211725 | ||
![]() |
84667fd32d | ||
![]() |
e42a6c5d4f | ||
![]() |
6f926d0a66 | ||
![]() |
9f801e7785 | ||
![]() |
f0b86c512d | ||
![]() |
3383e8b70d | ||
![]() |
058aed96d2 | ||
![]() |
b1285fcc4e | ||
![]() |
d56680e05e | ||
![]() |
5cf3bea8fe | ||
![]() |
b48ab77a38 | ||
![]() |
cdead8661d | ||
![]() |
474d427b87 | ||
![]() |
87f726141a | ||
![]() |
fa57d57215 | ||
![]() |
8ea2d40467 | ||
![]() |
d5af542dd1 | ||
![]() |
55895df54d | ||
![]() |
b07ac301b9 | ||
![]() |
473a5559cc | ||
![]() |
c92ee120b6 | ||
![]() |
3bba781554 | ||
![]() |
bc6eb94c0d | ||
![]() |
5526585eeb | ||
![]() |
f32bb1a318 | ||
![]() |
e710d3699c | ||
![]() |
26e3624610 | ||
![]() |
427aa55789 | ||
![]() |
ffa6f42c0e | ||
![]() |
f5412dd209 | ||
![]() |
6d58dd541e | ||
![]() |
6a743310bb | ||
![]() |
ab56a4ca69 | ||
![]() |
db9a805ff0 | ||
![]() |
e78dc486f7 | ||
![]() |
1f6658fca0 | ||
![]() |
bad109dec5 | ||
![]() |
892b78a1f9 | ||
![]() |
70c355b52e | ||
![]() |
d3376f31d0 | ||
![]() |
06019e7995 | ||
![]() |
2f1ff5ab95 | ||
![]() |
593ae48aa2 | ||
![]() |
25cfd6ceda | ||
![]() |
7197b8ebff | ||
![]() |
7b736908fa | ||
![]() |
e858e21a40 | ||
![]() |
2f44e30013 | ||
![]() |
b88d662677 | ||
![]() |
7aeefa1400 | ||
![]() |
6fb6f92985 | ||
![]() |
f50325fc7d | ||
![]() |
0b41d056d3 | ||
![]() |
d8bcba9ef0 | ||
![]() |
d309239bcc | ||
![]() |
a13911e00e | ||
![]() |
0ba5711603 | ||
![]() |
36cbd28d9d | ||
![]() |
a12915fc14 | ||
![]() |
7b7483b254 | ||
![]() |
95afebceb4 | ||
![]() |
3d5e4b980f | ||
![]() |
f2f653efcf | ||
![]() |
b5c7bdd98f | ||
![]() |
ad126a745a | ||
![]() |
38e6133202 | ||
![]() |
8541dc5bde | ||
![]() |
5327996bad | ||
![]() |
4ddc43a9d9 | ||
![]() |
e6dea4179b | ||
![]() |
0318b85517 | ||
![]() |
29987d443e | ||
![]() |
cbfd8707b9 | ||
![]() |
5f158f5c87 | ||
![]() |
d67ccd2fce | ||
![]() |
29c9d3804b | ||
![]() |
76d478c84f | ||
![]() |
5d9d6f099c | ||
![]() |
e4b31640b3 | ||
![]() |
c43f6a67d0 | ||
![]() |
490dd3b525 | ||
![]() |
0bbab63193 | ||
![]() |
06188b8fbd | ||
![]() |
bbbb5cadd4 | ||
![]() |
52fcdda429 | ||
![]() |
7d93ceb0f0 | ||
![]() |
873e4b77eb | ||
![]() |
61f0eabcbb | ||
![]() |
134b5319e1 | ||
![]() |
ee78e21950 | ||
![]() |
323bc54efc | ||
![]() |
fd2dee3c11 | ||
![]() |
fc53322c07 | ||
![]() |
faf9977abb | ||
![]() |
7336c8fc07 | ||
![]() |
5cfaeda95b | ||
![]() |
a78e9039c6 | ||
![]() |
227f3cea25 | ||
![]() |
cab4890246 | ||
![]() |
95fd096bdd | ||
![]() |
91cf8cb547 | ||
![]() |
3ce4f3f918 | ||
![]() |
71159c755f | ||
![]() |
3c57b12cd1 | ||
![]() |
ca33d7263f | ||
![]() |
6c54f8dff2 | ||
![]() |
7bdec5f19f | ||
![]() |
8edecd8671 | ||
![]() |
536109251e | ||
![]() |
d82c30364a | ||
![]() |
ca5ce74740 | ||
![]() |
1c242a6602 | ||
![]() |
0e7a083847 | ||
![]() |
13e9906929 | ||
![]() |
25d6974137 | ||
![]() |
d2124db3ec | ||
![]() |
bc6d342919 | ||
![]() |
daaa1486fc | ||
![]() |
7826bb9323 | ||
![]() |
289e94f270 | ||
![]() |
b160ce21fc | ||
![]() |
98cf936ff5 | ||
![]() |
d3a96ba688 | ||
![]() |
47a9f25ba6 | ||
![]() |
3199b538ee | ||
![]() |
4f25296c50 | ||
![]() |
52408e67b2 | ||
![]() |
3b115506b9 | ||
![]() |
6e2148193a | ||
![]() |
e0f4da390a | ||
![]() |
a45ce3083b | ||
![]() |
711f9ab900 | ||
![]() |
cdff2e4648 | ||
![]() |
6b601b9aad | ||
![]() |
873cf6ac09 | ||
![]() |
a65bf35a06 | ||
![]() |
b6df07b2ed | ||
![]() |
2e79db3695 | ||
![]() |
37213503b1 | ||
![]() |
bf50ee9b5e | ||
![]() |
058c965b88 | ||
![]() |
8fe45fb994 | ||
![]() |
49a62d5294 | ||
![]() |
1665d9474f | ||
![]() |
d498dbd5ac | ||
![]() |
8807e326d1 | ||
![]() |
290116029b | ||
![]() |
ed20947e30 | ||
![]() |
e4e476f83e | ||
![]() |
8620309f9e | ||
![]() |
688d5bb4c9 | ||
![]() |
9edec57a82 | ||
![]() |
00fc3f294b | ||
![]() |
f5c73027bb | ||
![]() |
219b441be0 | ||
![]() |
76e76a417c | ||
![]() |
994bf27024 | ||
![]() |
6284a83a34 | ||
![]() |
25f15c1149 | ||
![]() |
e831b1b230 | ||
![]() |
a3e981f148 | ||
![]() |
0abe7514b9 | ||
![]() |
40292a154d | ||
![]() |
8192f2ef2e | ||
![]() |
b3d640982d | ||
![]() |
93982241a2 | ||
![]() |
3c6b49b34f | ||
![]() |
1766f87620 | ||
![]() |
717e5b95e6 | ||
![]() |
ff622af888 | ||
![]() |
8b4d9f96d4 | ||
![]() |
f3a43e273a | ||
![]() |
7eeb3df1c2 | ||
![]() |
7cbcdbe610 | ||
![]() |
8a51644d1d | ||
![]() |
befcd63221 | ||
![]() |
264d4a53a2 | ||
![]() |
8a67e89e91 | ||
![]() |
e8069e1c07 | ||
![]() |
1a46edffaa | ||
![]() |
e2d4e8b65d | ||
![]() |
f1a6e949c0 | ||
![]() |
03aff0d662 | ||
![]() |
d9d47f7203 | ||
![]() |
4e7dd92a3d | ||
![]() |
60db355577 | ||
![]() |
aa2a1fc5ef | ||
![]() |
6675b497bd | ||
![]() |
f0c5e00cc1 | ||
![]() |
ffcc0496f1 | ||
![]() |
0d3011f0fb | ||
![]() |
9aa8a786a5 | ||
![]() |
62c025fd12 | ||
![]() |
be67f320b5 | ||
![]() |
b0b28bd98a | ||
![]() |
40f92bac93 | ||
![]() |
726bd5b012 | ||
![]() |
9bbf4fe8c1 | ||
![]() |
e0cff8de84 | ||
![]() |
e54febdc1e | ||
![]() |
d94bdb7ecd | ||
![]() |
06fd6442b6 | ||
![]() |
b910bc7802 | ||
![]() |
2d22a60b8f | ||
![]() |
6754bf2466 | ||
![]() |
244b666dee | ||
![]() |
105d9d5970 | ||
![]() |
81e6b93529 | ||
![]() |
7507a9c24e | ||
![]() |
1aed112c2c | ||
![]() |
817597b07a | ||
![]() |
2c68be3f7e | ||
![]() |
f209d75f2c | ||
![]() |
ea33925afc | ||
![]() |
f399ffae72 | ||
![]() |
3a2b446e33 | ||
![]() |
deea19db51 | ||
![]() |
e2c050ed40 | ||
![]() |
02e9002466 | ||
![]() |
d4f205c366 | ||
![]() |
b7094c12f7 | ||
![]() |
293d455cba | ||
![]() |
99b5adaef1 | ||
![]() |
aa556d8678 | ||
![]() |
9b14faa43d | ||
![]() |
52838d8b84 | ||
![]() |
ccb0be9df4 | ||
![]() |
3b03a37f3b | ||
![]() |
3ccb7d80f3 | ||
![]() |
4e89948b5c | ||
![]() |
fe34e6beee | ||
![]() |
9f95383201 | ||
![]() |
7e452521c8 | ||
![]() |
991de6f1d0 | ||
![]() |
be32e3fe8f | ||
![]() |
d6eb61e9ec | ||
![]() |
e74fe69d65 | ||
![]() |
208406123e | ||
![]() |
8bcd135f3d | ||
![]() |
e7ea0e435e | ||
![]() |
b15b680cfe | ||
![]() |
5e26d98bdf | ||
![]() |
9f94ee280a | ||
![]() |
efa98539fa | ||
![]() |
113cd4bfcc | ||
![]() |
ccbaf76e44 | ||
![]() |
5d9d93d3a1 | ||
![]() |
c2c5274aac | ||
![]() |
89756394c9 | ||
![]() |
352aa88e79 | ||
![]() |
714962bd7a | ||
![]() |
fb4c50b5dc | ||
![]() |
b4794b2029 | ||
![]() |
3a8c8accfe | ||
![]() |
844adfc590 | ||
![]() |
a279e23fb5 | ||
![]() |
af9bbd0585 | ||
![]() |
1304194f09 | ||
![]() |
e909417a3f | ||
![]() |
02706c116d | ||
![]() |
3af6b5cb4c | ||
![]() |
35c1bb1ec5 | ||
![]() |
e51154ae69 | ||
![]() |
cd2ce5e11b | ||
![]() |
27964e16c1 | ||
![]() |
edd2d4c349 | ||
![]() |
0aa09a2d51 | ||
![]() |
62e45e393d | ||
![]() |
eadff2938f | ||
![]() |
354cd90c92 | ||
![]() |
11348959ca | ||
![]() |
935890e4e0 | ||
![]() |
82d5304b45 | ||
![]() |
2401d8900a | ||
![]() |
c834944ee7 | ||
![]() |
9a90e1e410 | ||
![]() |
73ef240921 | ||
![]() |
2985f08054 | ||
![]() |
8780bc99eb | ||
![]() |
452fbbe61c | ||
![]() |
6be8370eb3 | ||
![]() |
9682d3b313 | ||
![]() |
d47481a30e | ||
![]() |
3dd1fadc7d | ||
![]() |
fd1044dcba | ||
![]() |
2aa584ce39 | ||
![]() |
e78139edf1 | ||
![]() |
eb49e596f9 | ||
![]() |
eaad8ec49d | ||
![]() |
99e1a7a676 | ||
![]() |
4ff2309a90 | ||
![]() |
4bafdf5e4b | ||
![]() |
f38a32477e | ||
![]() |
59073d47a1 | ||
![]() |
df1563daaf | ||
![]() |
93dfbb4166 | ||
![]() |
9549b1488e | ||
![]() |
6ba45a32c0 | ||
![]() |
88f18fdfdc | ||
![]() |
377e0a64d1 | ||
![]() |
1a4a3a0f08 | ||
![]() |
485da61d3c | ||
![]() |
5d7b60e4c8 | ||
![]() |
5d8e03c124 | ||
![]() |
edc763b7d2 | ||
![]() |
c51e644203 | ||
![]() |
dc4464a347 | ||
![]() |
47919fe7e9 | ||
![]() |
6455daf092 | ||
![]() |
e06af94a1a | ||
![]() |
052eed6bb3 | ||
![]() |
095b04caf9 | ||
![]() |
83dd1af6d2 | ||
![]() |
8bfffcbd29 | ||
![]() |
f2b07ea886 | ||
![]() |
4f255439eb | ||
![]() |
b44c26d324 | ||
![]() |
46f4bc3434 | ||
![]() |
b280874dc0 | ||
![]() |
aec6868af1 | ||
![]() |
48865e00b6 | ||
![]() |
a5002018e0 | ||
![]() |
8e35783164 | ||
![]() |
f8e3f2a94f | ||
![]() |
cc30823726 | ||
![]() |
97cc3984c5 | ||
![]() |
cc5c8bf5e3 | ||
![]() |
cfaf18f942 | ||
![]() |
98e317dd55 | ||
![]() |
1f24e5aec4 | ||
![]() |
ed088aa72f | ||
![]() |
51162320cb | ||
![]() |
b88eab8ba3 | ||
![]() |
6c080ee650 | ||
![]() |
8056b0df2b | ||
![]() |
3f94b7a61c | ||
![]() |
1484e46317 | ||
![]() |
2812c8a993 | ||
![]() |
cfe102f274 | ||
![]() |
b225a7f370 | ||
![]() |
0f3409bd09 | ||
![]() |
e7d371cddc | ||
![]() |
fffb414ba9 | ||
![]() |
1552aec416 | ||
![]() |
c69cec28fe | ||
![]() |
61e0b938ae | ||
![]() |
c0e5a549b6 | ||
![]() |
f0bba1d6d4 | ||
![]() |
1c1a950c05 | ||
![]() |
df2248bb82 | ||
![]() |
5043e2ad10 | ||
![]() |
2c2fd76270 | ||
![]() |
7fe75a959f | ||
![]() |
5f88354cb3 | ||
![]() |
09561aeb39 | ||
![]() |
7001f8daaf | ||
![]() |
b41fc932c5 | ||
![]() |
2a11c413c7 | ||
![]() |
0872243297 | ||
![]() |
bba889975a | ||
![]() |
36412a034d | ||
![]() |
13dfd27b7e | ||
![]() |
1fb02944b7 | ||
![]() |
1c045ab222 | ||
![]() |
0d329bd83d | ||
![]() |
d1995086cc | ||
![]() |
f0ad0e6eae | ||
![]() |
457a7216ff | ||
![]() |
782f504522 | ||
![]() |
e60a284354 | ||
![]() |
d5d9bc1df6 | ||
![]() |
27fd0a88f4 | ||
![]() |
24188ffb31 | ||
![]() |
e51d9bd6f4 | ||
![]() |
3eb7302fde | ||
![]() |
49b2f8fd7f | ||
![]() |
0143a71e97 | ||
![]() |
9bc806ab21 | ||
![]() |
366c5c3f10 | ||
![]() |
3ee5262a8d | ||
![]() |
c671862d3f | ||
![]() |
50ba93042b | ||
![]() |
01e8ca6495 | ||
![]() |
7d82375f81 | ||
![]() |
47033e587b | ||
![]() |
e73b08b269 | ||
![]() |
a195a9107b | ||
![]() |
185949cc18 | ||
![]() |
c129f27c95 | ||
![]() |
1456d9d800 | ||
![]() |
3b9bb96784 | ||
![]() |
7359013db0 | ||
![]() |
be3d678f23 | ||
![]() |
e8099fd3b2 | ||
![]() |
344cfedd6f | ||
![]() |
c0d882e305 | ||
![]() |
e86fc88631 | ||
![]() |
e1127fc78c | ||
![]() |
95fbba1d74 | ||
![]() |
46ac44c248 | ||
![]() |
0ebdb1c2a8 | ||
![]() |
e3a90831bf | ||
![]() |
ec100e5a6c | ||
![]() |
0eb087ba3f | ||
![]() |
e55757dc82 | ||
![]() |
c51a2317e1 | ||
![]() |
7fb949dff7 | ||
![]() |
74ea553b63 | ||
![]() |
d9690507a4 | ||
![]() |
e69b4f389f | ||
![]() |
8a97c2bfca | ||
![]() |
d5ba55d2fc | ||
![]() |
d38e046494 | ||
![]() |
6a5a66e2f9 | ||
![]() |
db63d9fcbf | ||
![]() |
5b3d798eca | ||
![]() |
a0dde2a7d6 | ||
![]() |
1bdc33d52d | ||
![]() |
f1d332da5a | ||
![]() |
304c13261a | ||
![]() |
c58cbfd6f4 | ||
![]() |
b890d3e15a | ||
![]() |
2c9b8b6835 | ||
![]() |
73cc1f51ca | ||
![]() |
dca77e8232 | ||
![]() |
03cb177e7c | ||
![]() |
ad04b53615 | ||
![]() |
46bcb307f6 | ||
![]() |
b816625028 | ||
![]() |
0940fc7806 | ||
![]() |
50aefc3653 | ||
![]() |
c0dc83cbc0 | ||
![]() |
50cec420ef | ||
![]() |
23dac3933f | ||
![]() |
32f59bfd25 | ||
![]() |
4f36bbdfe6 | ||
![]() |
973fee9fe1 | ||
![]() |
13001faf51 | ||
![]() |
9f780a5308 | ||
![]() |
d87c963db5 | ||
![]() |
c6a9472fdb | ||
![]() |
cd0a983850 | ||
![]() |
890d3f4af4 | ||
![]() |
a778092941 | ||
![]() |
9ea582de26 | ||
![]() |
b6f2d8f30b | ||
![]() |
139072bb59 | ||
![]() |
07a93dade2 | ||
![]() |
9dc04cb088 | ||
![]() |
890c672f8c | ||
![]() |
e28e4d210f | ||
![]() |
dcd2d42894 | ||
![]() |
e47e151259 | ||
![]() |
2c44043e6a | ||
![]() |
f248901ea8 | ||
![]() |
62b6be900f | ||
![]() |
1b15df3075 | ||
![]() |
229407d685 | ||
![]() |
aaecb47125 | ||
![]() |
b17ee78dec | ||
![]() |
20e48054cf | ||
![]() |
ee486c269c | ||
![]() |
aee891434f | ||
![]() |
5472345f45 | ||
![]() |
572534b306 | ||
![]() |
3c363eb5ce | ||
![]() |
7e4432e321 | ||
![]() |
5ae7109561 | ||
![]() |
4602c0a1c3 | ||
![]() |
53bc5ff029 | ||
![]() |
c782a6ab63 | ||
![]() |
23644a60ac | ||
![]() |
14e66ffef4 | ||
![]() |
fa40d02a07 | ||
![]() |
8382663be4 | ||
![]() |
7e1309d874 | ||
![]() |
1d0cba1a43 | ||
![]() |
7d9a6ceb6b | ||
![]() |
6abdb28a03 | ||
![]() |
3690e03951 | ||
![]() |
4fe4d14f16 | ||
![]() |
74e8ffa555 | ||
![]() |
c257b228f1 | ||
![]() |
6ff0f67d03 | ||
![]() |
8fdff9ca37 | ||
![]() |
9055dff9bd | ||
![]() |
e766d681b5 | ||
![]() |
511e57d0b3 | ||
![]() |
74be49d00d | ||
![]() |
684c3aac6b | ||
![]() |
a718b6ebff | ||
![]() |
f17274d417 | ||
![]() |
1530139a61 | ||
![]() |
f56d65b2ec | ||
![]() |
21277a81d3 | ||
![]() |
e1ce5b8c69 | ||
![]() |
0323a9c4e6 | ||
![]() |
c7d89398a0 | ||
![]() |
8cc587d3a7 | ||
![]() |
5ad156767a | ||
![]() |
f54b3f4de2 | ||
![]() |
6f0c62dc9d | ||
![]() |
dce8bca103 | ||
![]() |
22af8af132 | ||
![]() |
8a62b882bf | ||
![]() |
708f22fe6f | ||
![]() |
a4e71e2055 | ||
![]() |
61a3cc37e0 | ||
![]() |
a0668e5a5b | ||
![]() |
b4b7142b55 | ||
![]() |
108b71d33c | ||
![]() |
2636a47333 | ||
![]() |
17116fcd6c | ||
![]() |
17c16144d1 | ||
![]() |
178d509d56 | ||
![]() |
09c129de40 | ||
![]() |
07128ba063 | ||
![]() |
a786ff53ff | ||
![]() |
d2e19c829d | ||
![]() |
94b342f26a | ||
![]() |
9e3e6b3f43 | ||
![]() |
4300900322 | ||
![]() |
342e04974d | ||
![]() |
fdb4c0a81f | ||
![]() |
6de878ffe4 | ||
![]() |
c63aaec09e | ||
![]() |
d8bf47c101 | ||
![]() |
736ff8828d | ||
![]() |
b501999a4c | ||
![]() |
3985f1c6c8 | ||
![]() |
46ec3987a8 | ||
![]() |
df4e5a54e3 | ||
![]() |
d8a259044f | ||
![]() |
0891669aee | ||
![]() |
83c0351338 | ||
![]() |
c5e5fe555d | ||
![]() |
345ba73777 | ||
![]() |
e4200a79a2 | ||
![]() |
381fa65ba0 | ||
![]() |
16314711b8 | ||
![]() |
553abe4a4a | ||
![]() |
6a1bbdb3a7 | ||
![]() |
59d92c75bd | ||
![]() |
7732e6878e | ||
![]() |
2cde317d59 | ||
![]() |
0c08430507 | ||
![]() |
fa6d7d5e3c | ||
![]() |
585b950a46 | ||
![]() |
3effc2e182 | ||
![]() |
0e1602ff71 | ||
![]() |
693584ce29 |
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,5 +1,6 @@
|
||||
name: Report an issue with Home Assistant Core
|
||||
description: Report an issue with Home Assistant Core.
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
34
.github/workflows/builder.yml
vendored
34
.github/workflows/builder.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
||||
|
||||
- name: Download translations
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: translations
|
||||
|
||||
@@ -190,14 +190,14 @@ jobs:
|
||||
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2025.02.0
|
||||
uses: home-assistant/builder@2025.03.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -256,14 +256,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2025.02.0
|
||||
uses: home-assistant/builder@2025.03.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -324,20 +324,20 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.8.1
|
||||
uses: sigstore/cosign-installer@v3.8.2
|
||||
with:
|
||||
cosign-release: "v2.2.3"
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: matrix.registry == 'docker.io/homeassistant'
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: matrix.registry == 'ghcr.io/home-assistant'
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -457,12 +457,12 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
- name: Download translations
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: translations
|
||||
|
||||
@@ -502,14 +502,14 @@ jobs:
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: . # So action will not pull the repository again
|
||||
file: ./script/hassfest/docker/Dockerfile
|
||||
@@ -522,7 +522,7 @@ jobs:
|
||||
- name: Push Docker image
|
||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||
id: push
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
||||
with:
|
||||
context: . # So action will not pull the repository again
|
||||
file: ./script/hassfest/docker/Dockerfile
|
||||
@@ -531,7 +531,7 @@ jobs:
|
||||
|
||||
- name: Generate artifact attestation
|
||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||
uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2
|
||||
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
|
||||
with:
|
||||
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
|
231
.github/workflows/ci.yaml
vendored
231
.github/workflows/ci.yaml
vendored
@@ -37,10 +37,10 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 11
|
||||
CACHE_VERSION: 12
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 9
|
||||
HA_SHORT_VERSION: "2025.4"
|
||||
HA_SHORT_VERSION: "2025.5"
|
||||
DEFAULT_PYTHON: "3.13"
|
||||
ALL_PYTHON_VERSIONS: "['3.13']"
|
||||
# 10.3 is the oldest supported version
|
||||
@@ -89,6 +89,7 @@ jobs:
|
||||
test_groups: ${{ steps.info.outputs.test_groups }}
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
tests: ${{ steps.info.outputs.tests }}
|
||||
lint_only: ${{ steps.info.outputs.lint_only }}
|
||||
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
@@ -142,6 +143,7 @@ jobs:
|
||||
test_group_count=10
|
||||
tests="[]"
|
||||
tests_glob=""
|
||||
lint_only=""
|
||||
skip_coverage=""
|
||||
|
||||
if [[ "${{ steps.integrations.outputs.changes }}" != "[]" ]];
|
||||
@@ -192,6 +194,17 @@ jobs:
|
||||
test_full_suite="true"
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.inputs.lint-only }}" == "true" ]] \
|
||||
|| [[ "${{ github.event.inputs.pylint-only }}" == "true" ]] \
|
||||
|| [[ "${{ github.event.inputs.mypy-only }}" == "true" ]] \
|
||||
|| [[ "${{ github.event.inputs.audit-licenses-only }}" == "true" ]] \
|
||||
|| [[ "${{ github.event_name }}" == "push" \
|
||||
&& "${{ github.event.repository.full_name }}" != "home-assistant/core" ]];
|
||||
then
|
||||
lint_only="true"
|
||||
skip_coverage="true"
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.inputs.skip-coverage }}" == "true" ]] \
|
||||
|| [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci-skip-coverage') }}" == "true" ]];
|
||||
then
|
||||
@@ -217,6 +230,8 @@ jobs:
|
||||
echo "tests=${tests}" >> $GITHUB_OUTPUT
|
||||
echo "tests_glob: ${tests_glob}"
|
||||
echo "tests_glob=${tests_glob}" >> $GITHUB_OUTPUT
|
||||
echo "lint_only": ${lint_only}
|
||||
echo "lint_only=${lint_only}" >> $GITHUB_OUTPUT
|
||||
echo "skip_coverage: ${skip_coverage}"
|
||||
echo "skip_coverage=${skip_coverage}" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -234,13 +249,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -256,7 +271,7 @@ jobs:
|
||||
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
lookup-only: true
|
||||
@@ -279,14 +294,14 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -295,7 +310,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -319,14 +334,14 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -335,7 +350,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -359,14 +374,14 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -375,7 +390,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -469,7 +484,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -482,7 +497,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -490,7 +505,7 @@ jobs:
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore uv wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: >-
|
||||
@@ -537,7 +552,7 @@ jobs:
|
||||
python --version
|
||||
uv pip freeze >> pip_freeze.txt
|
||||
- name: Upload pip_freeze artifact
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pip-freeze-${{ matrix.python-version }}
|
||||
path: pip_freeze.txt
|
||||
@@ -572,13 +587,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -605,13 +620,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -623,6 +638,25 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.gen_requirements_all validate
|
||||
|
||||
dependency-review:
|
||||
name: Dependency review
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& needs.info.outputs.requirements == 'true'
|
||||
&& github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Dependency review
|
||||
uses: actions/dependency-review-action@v4.6.0
|
||||
with:
|
||||
license-check: false # We use our own license audit checks
|
||||
|
||||
audit-licenses:
|
||||
name: Audit licenses
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -643,13 +677,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -661,7 +695,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.licenses extract --output-file=licenses-${{ matrix.python-version }}.json
|
||||
- name: Upload licenses
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
|
||||
path: licenses-${{ matrix.python-version }}.json
|
||||
@@ -686,13 +720,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -733,13 +767,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -778,7 +812,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -791,7 +825,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -799,7 +833,7 @@ jobs:
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@v4.2.2
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
@@ -829,11 +863,7 @@ jobs:
|
||||
prepare-pytest-full:
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& needs.info.outputs.test_full_suite == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -859,13 +889,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -877,7 +907,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.split_tests ${{ needs.info.outputs.test_group_count }} tests
|
||||
- name: Upload pytest_buckets
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pytest_buckets
|
||||
path: pytest_buckets.txt
|
||||
@@ -886,11 +916,7 @@ jobs:
|
||||
pytest-full:
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& needs.info.outputs.test_full_suite == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -923,13 +949,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -942,7 +968,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||
- name: Download pytest_buckets
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: pytest_buckets
|
||||
- name: Compile English translations
|
||||
@@ -962,6 +988,7 @@ jobs:
|
||||
if [[ "${{ needs.info.outputs.skip_coverage }}" != "true" ]]; then
|
||||
cov_params+=(--cov="homeassistant")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||
fi
|
||||
|
||||
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
|
||||
@@ -980,18 +1007,24 @@ jobs:
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-full.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: pytest-*.txt
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
overwrite: true
|
||||
- name: Upload test results artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: test-results-full-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: junit.xml
|
||||
- name: Remove pytest_buckets
|
||||
run: rm pytest_buckets.txt
|
||||
- name: Check dirty
|
||||
@@ -1009,11 +1042,7 @@ jobs:
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysqladmin ping -uroot -ppassword" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& needs.info.outputs.mariadb_groups != '[]'
|
||||
needs:
|
||||
- info
|
||||
@@ -1045,13 +1074,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1088,6 +1117,7 @@ jobs:
|
||||
cov_params+=(--cov="homeassistant.components.recorder")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||
fi
|
||||
|
||||
python3 -b -X dev -m pytest \
|
||||
@@ -1108,7 +1138,7 @@ jobs:
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${mariadb}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
@@ -1116,12 +1146,19 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
path: coverage.xml
|
||||
overwrite: true
|
||||
- name: Upload test results artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: test-results-mariadb-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
path: junit.xml
|
||||
- name: Check dirty
|
||||
run: |
|
||||
./script/check_dirty
|
||||
@@ -1137,11 +1174,7 @@ jobs:
|
||||
POSTGRES_PASSWORD: password
|
||||
options: --health-cmd="pg_isready -hlocalhost -Upostgres" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& needs.info.outputs.postgresql_groups != '[]'
|
||||
needs:
|
||||
- info
|
||||
@@ -1175,13 +1208,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1218,6 +1251,7 @@ jobs:
|
||||
cov_params+=(--cov="homeassistant.components.recorder")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||
fi
|
||||
|
||||
python3 -b -X dev -m pytest \
|
||||
@@ -1239,7 +1273,7 @@ jobs:
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${postgresql}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
@@ -1247,12 +1281,19 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
path: coverage.xml
|
||||
overwrite: true
|
||||
- name: Upload test results artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: test-results-postgres-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
path: junit.xml
|
||||
- name: Check dirty
|
||||
run: |
|
||||
./script/check_dirty
|
||||
@@ -1271,12 +1312,12 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
uses: codecov/codecov-action@v5.4.2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
flags: full-suite
|
||||
@@ -1285,11 +1326,7 @@ jobs:
|
||||
pytest-partial:
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
(github.event_name != 'push' || github.event.repository.full_name == 'home-assistant/core')
|
||||
&& github.event.inputs.lint-only != 'true'
|
||||
&& github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs.info.outputs.lint_only != 'true'
|
||||
&& needs.info.outputs.tests_glob
|
||||
&& needs.info.outputs.test_full_suite == 'false'
|
||||
needs:
|
||||
@@ -1322,13 +1359,13 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.2.2
|
||||
uses: actions/cache/restore@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1365,6 +1402,7 @@ jobs:
|
||||
cov_params+=(--cov="homeassistant.components.${{ matrix.group }}")
|
||||
cov_params+=(--cov-report=xml)
|
||||
cov_params+=(--cov-report=term-missing)
|
||||
cov_params+=(--junitxml=junit.xml -o junit_family=legacy)
|
||||
fi
|
||||
|
||||
python3 -b -X dev -m pytest \
|
||||
@@ -1382,18 +1420,24 @@ jobs:
|
||||
2>&1 | tee pytest-${{ matrix.python-version }}-${{ matrix.group }}.txt
|
||||
- name: Upload pytest output
|
||||
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: pytest-*.txt
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
overwrite: true
|
||||
- name: Upload test results artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: test-results-partial-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: junit.xml
|
||||
- name: Check dirty
|
||||
run: |
|
||||
./script/check_dirty
|
||||
@@ -1410,12 +1454,37 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
uses: codecov/codecov-action@v5.4.0
|
||||
uses: codecov/codecov-action@v5.4.2
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
upload-test-results:
|
||||
name: Upload test results to Codecov
|
||||
# codecov/test-results-action currently doesn't support tokenless uploads
|
||||
# therefore we can't run it on forks
|
||||
if: ${{ (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) && needs.info.outputs.skip_coverage != 'true' && !cancelled() }}
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pytest-partial
|
||||
- pytest-full
|
||||
- pytest-postgres
|
||||
- pytest-mariadb
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
pattern: test-results-*
|
||||
- name: Upload test results to Codecov
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.28.10
|
||||
uses: github/codeql-action/init@v3.28.16
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.28.10
|
||||
uses: github/codeql-action/analyze@v3.28.16
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
28
.github/workflows/wheels.yml
vendored
28
.github/workflows/wheels.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.4.0
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
) > build_constraints.txt
|
||||
|
||||
- name: Upload env_file
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: env_file
|
||||
path: ./.env_file
|
||||
@@ -99,14 +99,14 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
- name: Upload build_constraints
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: build_constraints
|
||||
path: ./build_constraints.txt
|
||||
overwrite: true
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: requirements_diff
|
||||
path: ./requirements_diff.txt
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
python -m script.gen_requirements_all ci
|
||||
|
||||
- name: Upload requirements_all_wheels
|
||||
uses: actions/upload-artifact@v4.6.1
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: requirements_all_wheels
|
||||
path: ./requirements_all_wheels_*.txt
|
||||
@@ -138,17 +138,17 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- name: Download build_constraints
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: build_constraints
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.02.0
|
||||
uses: home-assistant/wheels@2025.03.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -187,22 +187,22 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- name: Download build_constraints
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: build_constraints
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Download requirements_all_wheels
|
||||
uses: actions/download-artifact@v4.1.9
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
with:
|
||||
name: requirements_all_wheels
|
||||
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.02.0
|
||||
uses: home-assistant/wheels@2025.03.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -69,6 +69,7 @@ test-reports/
|
||||
test-results.xml
|
||||
test-output.xml
|
||||
pytest-*.txt
|
||||
junit.xml
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.9.8
|
||||
rev: v0.11.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
|
@@ -119,6 +119,7 @@ homeassistant.components.bluetooth_adapters.*
|
||||
homeassistant.components.bluetooth_tracker.*
|
||||
homeassistant.components.bmw_connected_drive.*
|
||||
homeassistant.components.bond.*
|
||||
homeassistant.components.bosch_alarm.*
|
||||
homeassistant.components.braviatv.*
|
||||
homeassistant.components.bring.*
|
||||
homeassistant.components.brother.*
|
||||
@@ -136,6 +137,7 @@ homeassistant.components.clicksend.*
|
||||
homeassistant.components.climate.*
|
||||
homeassistant.components.cloud.*
|
||||
homeassistant.components.co2signal.*
|
||||
homeassistant.components.comelit.*
|
||||
homeassistant.components.command_line.*
|
||||
homeassistant.components.config.*
|
||||
homeassistant.components.configurator.*
|
||||
@@ -289,6 +291,7 @@ homeassistant.components.kaleidescape.*
|
||||
homeassistant.components.knocki.*
|
||||
homeassistant.components.knx.*
|
||||
homeassistant.components.kraken.*
|
||||
homeassistant.components.kulersky.*
|
||||
homeassistant.components.lacrosse.*
|
||||
homeassistant.components.lacrosse_view.*
|
||||
homeassistant.components.lamarzocco.*
|
||||
@@ -360,8 +363,10 @@ homeassistant.components.no_ip.*
|
||||
homeassistant.components.nordpool.*
|
||||
homeassistant.components.notify.*
|
||||
homeassistant.components.notion.*
|
||||
homeassistant.components.ntfy.*
|
||||
homeassistant.components.number.*
|
||||
homeassistant.components.nut.*
|
||||
homeassistant.components.ohme.*
|
||||
homeassistant.components.onboarding.*
|
||||
homeassistant.components.oncue.*
|
||||
homeassistant.components.onedrive.*
|
||||
@@ -381,6 +386,7 @@ homeassistant.components.pandora.*
|
||||
homeassistant.components.panel_custom.*
|
||||
homeassistant.components.peblar.*
|
||||
homeassistant.components.peco.*
|
||||
homeassistant.components.pegel_online.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.person.*
|
||||
homeassistant.components.pi_hole.*
|
||||
@@ -411,6 +417,7 @@ homeassistant.components.recollect_waste.*
|
||||
homeassistant.components.recorder.*
|
||||
homeassistant.components.remember_the_milk.*
|
||||
homeassistant.components.remote.*
|
||||
homeassistant.components.remote_calendar.*
|
||||
homeassistant.components.renault.*
|
||||
homeassistant.components.reolink.*
|
||||
homeassistant.components.repairs.*
|
||||
@@ -456,6 +463,7 @@ homeassistant.components.slack.*
|
||||
homeassistant.components.sleepiq.*
|
||||
homeassistant.components.smhi.*
|
||||
homeassistant.components.smlight.*
|
||||
homeassistant.components.smtp.*
|
||||
homeassistant.components.snooz.*
|
||||
homeassistant.components.solarlog.*
|
||||
homeassistant.components.sonarr.*
|
||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"label": "Run Home Assistant Core",
|
||||
"type": "shell",
|
||||
"command": "hass -c ./config",
|
||||
"command": "${command:python.interpreterPath} -m homeassistant -c ./config",
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
|
38
CODEOWNERS
generated
38
CODEOWNERS
generated
@@ -216,6 +216,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/bmw_connected_drive/ @gerard33 @rikroe
|
||||
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
||||
/tests/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
|
||||
/homeassistant/components/bosch_alarm/ @mag1024 @sanjay900
|
||||
/tests/components/bosch_alarm/ @mag1024 @sanjay900
|
||||
/homeassistant/components/bosch_shc/ @tschamm
|
||||
/tests/components/bosch_shc/ @tschamm
|
||||
/homeassistant/components/braviatv/ @bieniu @Drafteed
|
||||
@@ -430,7 +432,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/entur_public_transport/ @hfurubotten
|
||||
/homeassistant/components/environment_canada/ @gwww @michaeldavie
|
||||
/tests/components/environment_canada/ @gwww @michaeldavie
|
||||
/homeassistant/components/ephember/ @ttroy50
|
||||
/homeassistant/components/ephember/ @ttroy50 @roberty99
|
||||
/homeassistant/components/epic_games_store/ @hacf-fr @Quentame
|
||||
/tests/components/epic_games_store/ @hacf-fr @Quentame
|
||||
/homeassistant/components/epion/ @lhgravendeel
|
||||
@@ -570,8 +572,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/google_cloud/ @lufton @tronikos
|
||||
/homeassistant/components/google_drive/ @tronikos
|
||||
/tests/components/google_drive/ @tronikos
|
||||
/homeassistant/components/google_generative_ai_conversation/ @tronikos
|
||||
/tests/components/google_generative_ai_conversation/ @tronikos
|
||||
/homeassistant/components/google_generative_ai_conversation/ @tronikos @ivanlh
|
||||
/tests/components/google_generative_ai_conversation/ @tronikos @ivanlh
|
||||
/homeassistant/components/google_mail/ @tkdrob
|
||||
/tests/components/google_mail/ @tkdrob
|
||||
/homeassistant/components/google_photos/ @allenporter
|
||||
@@ -702,6 +704,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/image_upload/ @home-assistant/core
|
||||
/homeassistant/components/imap/ @jbouwh
|
||||
/tests/components/imap/ @jbouwh
|
||||
/homeassistant/components/imeon_inverter/ @Imeon-Energy
|
||||
/tests/components/imeon_inverter/ @Imeon-Energy
|
||||
/homeassistant/components/imgw_pib/ @bieniu
|
||||
/tests/components/imgw_pib/ @bieniu
|
||||
/homeassistant/components/improv_ble/ @emontnemery
|
||||
@@ -933,6 +937,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/metoffice/ @MrHarcombe @avee87
|
||||
/homeassistant/components/microbees/ @microBeesTech
|
||||
/tests/components/microbees/ @microBeesTech
|
||||
/homeassistant/components/miele/ @astrandb
|
||||
/tests/components/miele/ @astrandb
|
||||
/homeassistant/components/mikrotik/ @engrbm87
|
||||
/tests/components/mikrotik/ @engrbm87
|
||||
/homeassistant/components/mill/ @danielhiversen
|
||||
@@ -1045,6 +1051,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/nsw_fuel_station/ @nickw444
|
||||
/homeassistant/components/nsw_rural_fire_service_feed/ @exxamalte
|
||||
/tests/components/nsw_rural_fire_service_feed/ @exxamalte
|
||||
/homeassistant/components/ntfy/ @tr4nt0r
|
||||
/tests/components/ntfy/ @tr4nt0r
|
||||
/homeassistant/components/nuheat/ @tstabrawa
|
||||
/tests/components/nuheat/ @tstabrawa
|
||||
/homeassistant/components/nuki/ @pschmitt @pvizeli @pree
|
||||
@@ -1073,8 +1081,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/ombi/ @larssont
|
||||
/homeassistant/components/onboarding/ @home-assistant/core
|
||||
/tests/components/onboarding/ @home-assistant/core
|
||||
/homeassistant/components/oncue/ @bdraco @peterager
|
||||
/tests/components/oncue/ @bdraco @peterager
|
||||
/homeassistant/components/ondilo_ico/ @JeromeHXP
|
||||
/tests/components/ondilo_ico/ @JeromeHXP
|
||||
/homeassistant/components/onedrive/ @zweckj
|
||||
@@ -1183,6 +1189,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/prusalink/ @balloob
|
||||
/homeassistant/components/ps4/ @ktnrg45
|
||||
/tests/components/ps4/ @ktnrg45
|
||||
/homeassistant/components/pterodactyl/ @elmurato
|
||||
/tests/components/pterodactyl/ @elmurato
|
||||
/homeassistant/components/pure_energie/ @klaasnicolaas
|
||||
/tests/components/pure_energie/ @klaasnicolaas
|
||||
/homeassistant/components/purpleair/ @bachya
|
||||
@@ -1250,8 +1258,12 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/recovery_mode/ @home-assistant/core
|
||||
/homeassistant/components/refoss/ @ashionky
|
||||
/tests/components/refoss/ @ashionky
|
||||
/homeassistant/components/rehlko/ @bdraco @peterager
|
||||
/tests/components/rehlko/ @bdraco @peterager
|
||||
/homeassistant/components/remote/ @home-assistant/core
|
||||
/tests/components/remote/ @home-assistant/core
|
||||
/homeassistant/components/remote_calendar/ @Thomas55555
|
||||
/tests/components/remote_calendar/ @Thomas55555
|
||||
/homeassistant/components/renault/ @epenet
|
||||
/tests/components/renault/ @epenet
|
||||
/homeassistant/components/renson/ @jimmyd-be
|
||||
@@ -1306,6 +1318,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ruuvitag_ble/ @akx
|
||||
/homeassistant/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/tests/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/homeassistant/components/s3/ @tomasbedrich
|
||||
/tests/components/s3/ @tomasbedrich
|
||||
/homeassistant/components/sabnzbd/ @shaiu @jpbede
|
||||
/tests/components/sabnzbd/ @shaiu @jpbede
|
||||
/homeassistant/components/saj/ @fredericvl
|
||||
@@ -1381,7 +1395,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/siren/ @home-assistant/core @raman325
|
||||
/tests/components/siren/ @home-assistant/core @raman325
|
||||
/homeassistant/components/sisyphus/ @jkeljo
|
||||
/homeassistant/components/sky_hub/ @rogerselwyn
|
||||
/homeassistant/components/sky_remote/ @dunnmj @saty9
|
||||
/tests/components/sky_remote/ @dunnmj @saty9
|
||||
/homeassistant/components/skybell/ @tkdrob
|
||||
@@ -1428,8 +1441,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/solarlog/ @Ernst79 @dontinelli
|
||||
/homeassistant/components/solax/ @squishykid @Darsstar
|
||||
/tests/components/solax/ @squishykid @Darsstar
|
||||
/homeassistant/components/soma/ @ratsept @sebfortier2288
|
||||
/tests/components/soma/ @ratsept @sebfortier2288
|
||||
/homeassistant/components/soma/ @ratsept
|
||||
/tests/components/soma/ @ratsept
|
||||
/homeassistant/components/sonarr/ @ctalkington
|
||||
/tests/components/sonarr/ @ctalkington
|
||||
/homeassistant/components/songpal/ @rytilahti @shenxn
|
||||
@@ -1461,7 +1474,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/steam_online/ @tkdrob
|
||||
/homeassistant/components/steamist/ @bdraco
|
||||
/tests/components/steamist/ @bdraco
|
||||
/homeassistant/components/stiebel_eltron/ @fucm
|
||||
/homeassistant/components/stiebel_eltron/ @fucm @ThyMYthOS
|
||||
/tests/components/stiebel_eltron/ @fucm @ThyMYthOS
|
||||
/homeassistant/components/stookwijzer/ @fwestenberg
|
||||
/tests/components/stookwijzer/ @fwestenberg
|
||||
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
|
||||
@@ -1474,8 +1488,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/suez_water/ @ooii @jb101010-2
|
||||
/homeassistant/components/sun/ @Swamp-Ig
|
||||
/tests/components/sun/ @Swamp-Ig
|
||||
/homeassistant/components/sunweg/ @rokam
|
||||
/tests/components/sunweg/ @rokam
|
||||
/homeassistant/components/supla/ @mwegrzynek
|
||||
/homeassistant/components/surepetcare/ @benleb @danielhiversen
|
||||
/tests/components/surepetcare/ @benleb @danielhiversen
|
||||
@@ -1529,8 +1541,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/tedee/ @patrickhilker @zweckj
|
||||
/homeassistant/components/tellduslive/ @fredrike
|
||||
/tests/components/tellduslive/ @fredrike
|
||||
/homeassistant/components/template/ @PhracturedBlue @home-assistant/core
|
||||
/tests/components/template/ @PhracturedBlue @home-assistant/core
|
||||
/homeassistant/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||
/tests/components/template/ @Petro31 @PhracturedBlue @home-assistant/core
|
||||
/homeassistant/components/tesla_fleet/ @Bre77
|
||||
/tests/components/tesla_fleet/ @Bre77
|
||||
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
||||
|
4
Dockerfile
generated
4
Dockerfile
generated
@@ -25,13 +25,13 @@ RUN \
|
||||
"armv7") go2rtc_suffix='arm' ;; \
|
||||
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
||||
esac \
|
||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.8/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.9/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||
&& chmod +x /bin/go2rtc \
|
||||
# Verify go2rtc can be executed
|
||||
&& go2rtc --version
|
||||
|
||||
# Install uv
|
||||
RUN pip3 install uv==0.6.1
|
||||
RUN pip3 install uv==0.7.1
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
|
@@ -19,4 +19,4 @@ labels:
|
||||
org.opencontainers.image.authors: The Home Assistant Authors
|
||||
org.opencontainers.image.url: https://www.home-assistant.io/
|
||||
org.opencontainers.image.documentation: https://www.home-assistant.io/docs/
|
||||
org.opencontainers.image.licenses: Apache License 2.0
|
||||
org.opencontainers.image.licenses: Apache-2.0
|
||||
|
@@ -178,6 +178,15 @@ _BLOCKING_CALLS: tuple[BlockingCall, ...] = (
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=SSLContext.set_default_verify_paths,
|
||||
object=SSLContext,
|
||||
function="set_default_verify_paths",
|
||||
check_allowed=None,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.open,
|
||||
object=Path,
|
||||
|
@@ -53,6 +53,7 @@ from .components import (
|
||||
logbook as logbook_pre_import, # noqa: F401
|
||||
lovelace as lovelace_pre_import, # noqa: F401
|
||||
onboarding as onboarding_pre_import, # noqa: F401
|
||||
person as person_pre_import, # noqa: F401
|
||||
recorder as recorder_import, # noqa: F401 - not named pre_import since it has requirements
|
||||
repairs as repairs_pre_import, # noqa: F401
|
||||
search as search_pre_import, # noqa: F401
|
||||
@@ -81,6 +82,7 @@ from .helpers import (
|
||||
entity,
|
||||
entity_registry,
|
||||
floor_registry,
|
||||
frame,
|
||||
issue_registry,
|
||||
label_registry,
|
||||
recorder,
|
||||
@@ -92,6 +94,7 @@ from .helpers.dispatcher import async_dispatcher_send_internal
|
||||
from .helpers.storage import get_internal_store_manager
|
||||
from .helpers.system_info import async_get_system_info
|
||||
from .helpers.typing import ConfigType
|
||||
from .loader import Integration
|
||||
from .setup import (
|
||||
# _setup_started is marked as protected to make it clear
|
||||
# that it is not part of the public API and should not be used
|
||||
@@ -298,14 +301,6 @@ async def async_setup_hass(
|
||||
|
||||
return hass
|
||||
|
||||
async def stop_hass(hass: core.HomeAssistant) -> None:
|
||||
"""Stop hass."""
|
||||
# Ask integrations to shut down. It's messy but we can't
|
||||
# do a clean stop without knowing what is broken
|
||||
with contextlib.suppress(TimeoutError):
|
||||
async with hass.timeout.async_timeout(10):
|
||||
await hass.async_stop()
|
||||
|
||||
hass = await create_hass()
|
||||
|
||||
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
||||
@@ -344,7 +339,7 @@ async def async_setup_hass(
|
||||
|
||||
if config_dict is None:
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
await hass.async_stop(force=True)
|
||||
hass = await create_hass()
|
||||
|
||||
elif not basic_setup_success:
|
||||
@@ -352,7 +347,7 @@ async def async_setup_hass(
|
||||
"Unable to set up core integrations. Activating recovery mode"
|
||||
)
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
await hass.async_stop(force=True)
|
||||
hass = await create_hass()
|
||||
|
||||
elif any(
|
||||
@@ -367,7 +362,7 @@ async def async_setup_hass(
|
||||
old_logging = hass.data.get(DATA_LOGGING)
|
||||
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
await hass.async_stop(force=True)
|
||||
hass = await create_hass()
|
||||
|
||||
if old_logging:
|
||||
@@ -441,9 +436,10 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
||||
if DATA_REGISTRIES_LOADED in hass.data:
|
||||
return
|
||||
hass.data[DATA_REGISTRIES_LOADED] = None
|
||||
translation.async_setup(hass)
|
||||
entity.async_setup(hass)
|
||||
frame.async_setup(hass)
|
||||
template.async_setup(hass)
|
||||
translation.async_setup(hass)
|
||||
await asyncio.gather(
|
||||
create_eager_task(get_internal_store_manager(hass).async_initialize()),
|
||||
create_eager_task(area_registry.async_load(hass)),
|
||||
@@ -664,11 +660,10 @@ def _create_log_file(
|
||||
err_handler = _RotatingFileHandlerWithoutShouldRollOver(
|
||||
err_log_path, backupCount=1
|
||||
)
|
||||
|
||||
try:
|
||||
err_handler.doRollover()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error rolling over log file: %s", err)
|
||||
try:
|
||||
err_handler.doRollover()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error rolling over log file: %s", err)
|
||||
|
||||
return err_handler
|
||||
|
||||
@@ -718,20 +713,25 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
|
||||
return domains
|
||||
|
||||
|
||||
async def _async_resolve_domains_to_setup(
|
||||
async def _async_resolve_domains_and_preload(
|
||||
hass: core.HomeAssistant, config: dict[str, Any]
|
||||
) -> tuple[set[str], dict[str, loader.Integration]]:
|
||||
"""Resolve all dependencies and return list of domains to set up."""
|
||||
) -> tuple[dict[str, Integration], dict[str, Integration]]:
|
||||
"""Resolve all dependencies and return integrations to set up.
|
||||
|
||||
The return value is a tuple of two dictionaries:
|
||||
- The first dictionary contains integrations
|
||||
specified by the configuration (including config entries).
|
||||
- The second dictionary contains the same integrations as the first dictionary
|
||||
together with all their dependencies.
|
||||
"""
|
||||
domains_to_setup = _get_domains(hass, config)
|
||||
needed_requirements: set[str] = set()
|
||||
platform_integrations = conf_util.extract_platform_integrations(
|
||||
config, BASE_PLATFORMS
|
||||
)
|
||||
# Ensure base platforms that have platform integrations are added to
|
||||
# to `domains_to_setup so they can be setup first instead of
|
||||
# discovering them when later when a config entry setup task
|
||||
# notices its needed and there is already a long line to use
|
||||
# the import executor.
|
||||
# Ensure base platforms that have platform integrations are added to `domains`,
|
||||
# so they can be setup first instead of discovering them later when a config
|
||||
# entry setup task notices that it's needed and there is already a long line
|
||||
# to use the import executor.
|
||||
#
|
||||
# For example if we have
|
||||
# sensor:
|
||||
@@ -747,111 +747,78 @@ async def _async_resolve_domains_to_setup(
|
||||
# so this will be less of a problem in the future.
|
||||
domains_to_setup.update(platform_integrations)
|
||||
|
||||
# Load manifests for base platforms and platform based integrations
|
||||
# that are defined under base platforms right away since we do not require
|
||||
# the manifest to list them as dependencies and we want to avoid the lock
|
||||
# contention when multiple integrations try to load them at once
|
||||
additional_manifests_to_load = {
|
||||
# Additionally process base platforms since we do not require the manifest
|
||||
# to list them as dependencies.
|
||||
# We want to later avoid lock contention when multiple integrations try to load
|
||||
# their manifests at once.
|
||||
# Also process integrations that are defined under base platforms
|
||||
# to speed things up.
|
||||
additional_domains_to_process = {
|
||||
*BASE_PLATFORMS,
|
||||
*chain.from_iterable(platform_integrations.values()),
|
||||
}
|
||||
|
||||
translations_to_load = additional_manifests_to_load.copy()
|
||||
|
||||
# Resolve all dependencies so we know all integrations
|
||||
# that will have to be loaded and start right-away
|
||||
integration_cache: dict[str, loader.Integration] = {}
|
||||
to_resolve: set[str] = domains_to_setup
|
||||
while to_resolve or additional_manifests_to_load:
|
||||
old_to_resolve: set[str] = to_resolve
|
||||
to_resolve = set()
|
||||
integrations_or_excs = await loader.async_get_integrations(
|
||||
hass, {*domains_to_setup, *additional_domains_to_process}
|
||||
)
|
||||
# Eliminate those missing or with invalid manifest
|
||||
integrations_to_process = {
|
||||
domain: itg
|
||||
for domain, itg in integrations_or_excs.items()
|
||||
if isinstance(itg, Integration)
|
||||
}
|
||||
integrations_dependencies = await loader.resolve_integrations_dependencies(
|
||||
hass, integrations_to_process.values()
|
||||
)
|
||||
# Eliminate those without valid dependencies
|
||||
integrations_to_process = {
|
||||
domain: integrations_to_process[domain] for domain in integrations_dependencies
|
||||
}
|
||||
|
||||
if additional_manifests_to_load:
|
||||
to_get = {*old_to_resolve, *additional_manifests_to_load}
|
||||
additional_manifests_to_load.clear()
|
||||
else:
|
||||
to_get = old_to_resolve
|
||||
integrations_to_setup = {
|
||||
domain: itg
|
||||
for domain, itg in integrations_to_process.items()
|
||||
if domain in domains_to_setup
|
||||
}
|
||||
all_integrations_to_setup = integrations_to_setup.copy()
|
||||
all_integrations_to_setup.update(
|
||||
(dep, loader.async_get_loaded_integration(hass, dep))
|
||||
for domain in integrations_to_setup
|
||||
for dep in integrations_dependencies[domain].difference(
|
||||
all_integrations_to_setup
|
||||
)
|
||||
)
|
||||
|
||||
manifest_deps: set[str] = set()
|
||||
resolve_dependencies_tasks: list[asyncio.Task[bool]] = []
|
||||
integrations_to_process: list[loader.Integration] = []
|
||||
|
||||
for domain, itg in (await loader.async_get_integrations(hass, to_get)).items():
|
||||
if not isinstance(itg, loader.Integration):
|
||||
continue
|
||||
integration_cache[domain] = itg
|
||||
needed_requirements.update(itg.requirements)
|
||||
|
||||
# Make sure manifests for dependencies are loaded in the next
|
||||
# loop to try to group as many as manifest loads in a single
|
||||
# call to avoid the creating one-off executor jobs later in
|
||||
# the setup process
|
||||
additional_manifests_to_load.update(
|
||||
dep
|
||||
for dep in chain(itg.dependencies, itg.after_dependencies)
|
||||
if dep not in integration_cache
|
||||
)
|
||||
|
||||
if domain not in old_to_resolve:
|
||||
continue
|
||||
|
||||
integrations_to_process.append(itg)
|
||||
manifest_deps.update(itg.dependencies)
|
||||
manifest_deps.update(itg.after_dependencies)
|
||||
if not itg.all_dependencies_resolved:
|
||||
resolve_dependencies_tasks.append(
|
||||
create_eager_task(
|
||||
itg.resolve_dependencies(),
|
||||
name=f"resolve dependencies {domain}",
|
||||
loop=hass.loop,
|
||||
)
|
||||
)
|
||||
|
||||
if unseen_deps := manifest_deps - integration_cache.keys():
|
||||
# If there are dependencies, try to preload all
|
||||
# the integrations manifest at once and add them
|
||||
# to the list of requirements we need to install
|
||||
# so we can try to check if they are already installed
|
||||
# in a single call below which avoids each integration
|
||||
# having to wait for the lock to do it individually
|
||||
deps = await loader.async_get_integrations(hass, unseen_deps)
|
||||
for dependant_domain, dependant_itg in deps.items():
|
||||
if isinstance(dependant_itg, loader.Integration):
|
||||
integration_cache[dependant_domain] = dependant_itg
|
||||
needed_requirements.update(dependant_itg.requirements)
|
||||
|
||||
if resolve_dependencies_tasks:
|
||||
await asyncio.gather(*resolve_dependencies_tasks)
|
||||
|
||||
for itg in integrations_to_process:
|
||||
try:
|
||||
all_deps = itg.all_dependencies
|
||||
except RuntimeError:
|
||||
# Integration.all_dependencies raises RuntimeError if
|
||||
# dependencies could not be resolved
|
||||
continue
|
||||
for dep in all_deps:
|
||||
if dep in domains_to_setup:
|
||||
continue
|
||||
domains_to_setup.add(dep)
|
||||
to_resolve.add(dep)
|
||||
|
||||
_LOGGER.info("Domains to be set up: %s", domains_to_setup)
|
||||
# Gather requirements for all integrations,
|
||||
# their dependencies and after dependencies.
|
||||
# To gather all the requirements we must ignore exceptions here.
|
||||
# The exceptions will be detected and handled later in the bootstrap process.
|
||||
integrations_after_dependencies = (
|
||||
await loader.resolve_integrations_after_dependencies(
|
||||
hass, integrations_to_process.values(), ignore_exceptions=True
|
||||
)
|
||||
)
|
||||
integrations_requirements = {
|
||||
domain: itg.requirements for domain, itg in integrations_to_process.items()
|
||||
}
|
||||
integrations_requirements.update(
|
||||
(dep, loader.async_get_loaded_integration(hass, dep).requirements)
|
||||
for deps in integrations_after_dependencies.values()
|
||||
for dep in deps.difference(integrations_requirements)
|
||||
)
|
||||
all_requirements = set(chain.from_iterable(integrations_requirements.values()))
|
||||
|
||||
# Optimistically check if requirements are already installed
|
||||
# ahead of setting up the integrations so we can prime the cache
|
||||
# We do not wait for this since its an optimization only
|
||||
# We do not wait for this since it's an optimization only
|
||||
hass.async_create_background_task(
|
||||
requirements.async_load_installed_versions(hass, needed_requirements),
|
||||
requirements.async_load_installed_versions(hass, all_requirements),
|
||||
"check installed requirements",
|
||||
eager_start=True,
|
||||
)
|
||||
|
||||
#
|
||||
# Only add the domains_to_setup after we finish resolving
|
||||
# as new domains are likely to added in the process
|
||||
#
|
||||
translations_to_load.update(domains_to_setup)
|
||||
# Start loading translations for all integrations we are going to set up
|
||||
# in the background so they are ready when we need them. This avoids a
|
||||
# lot of waiting for the translation load lock and a thundering herd of
|
||||
@@ -862,6 +829,7 @@ async def _async_resolve_domains_to_setup(
|
||||
# hold the translation load lock and if anything is fast enough to
|
||||
# wait for the translation load lock, loading will be done by the
|
||||
# time it gets to it.
|
||||
translations_to_load = {*all_integrations_to_setup, *additional_domains_to_process}
|
||||
hass.async_create_background_task(
|
||||
translation.async_load_integrations(hass, translations_to_load),
|
||||
"load translations",
|
||||
@@ -873,13 +841,13 @@ async def _async_resolve_domains_to_setup(
|
||||
# in the setup process.
|
||||
hass.async_create_background_task(
|
||||
get_internal_store_manager(hass).async_preload(
|
||||
[*PRELOAD_STORAGE, *domains_to_setup]
|
||||
[*PRELOAD_STORAGE, *all_integrations_to_setup]
|
||||
),
|
||||
"preload storage",
|
||||
eager_start=True,
|
||||
)
|
||||
|
||||
return domains_to_setup, integration_cache
|
||||
return integrations_to_setup, all_integrations_to_setup
|
||||
|
||||
|
||||
async def _async_set_up_integrations(
|
||||
@@ -889,69 +857,84 @@ async def _async_set_up_integrations(
|
||||
watcher = _WatchPendingSetups(hass, _setup_started(hass))
|
||||
watcher.async_start()
|
||||
|
||||
domains_to_setup, integration_cache = await _async_resolve_domains_to_setup(
|
||||
integrations, all_integrations = await _async_resolve_domains_and_preload(
|
||||
hass, config
|
||||
)
|
||||
stage_2_domains = domains_to_setup.copy()
|
||||
# Detect all cycles
|
||||
integrations_after_dependencies = (
|
||||
await loader.resolve_integrations_after_dependencies(
|
||||
hass, all_integrations.values(), set(all_integrations)
|
||||
)
|
||||
)
|
||||
all_domains = set(integrations_after_dependencies)
|
||||
domains = set(integrations) & all_domains
|
||||
|
||||
_LOGGER.info(
|
||||
"Domains to be set up: %s | %s",
|
||||
domains,
|
||||
all_domains - domains,
|
||||
)
|
||||
|
||||
async_set_domains_to_be_loaded(hass, all_domains)
|
||||
|
||||
# Initialize recorder
|
||||
if "recorder" in domains_to_setup:
|
||||
if "recorder" in all_domains:
|
||||
recorder.async_initialize_recorder(hass)
|
||||
|
||||
# Initialize backup
|
||||
if "backup" in domains_to_setup:
|
||||
if "backup" in all_domains:
|
||||
backup.async_initialize_backup(hass)
|
||||
|
||||
stage_0_and_1_domains: list[tuple[str, set[str], int | None]] = [
|
||||
stages: list[tuple[str, set[str], int | None]] = [
|
||||
*(
|
||||
(name, domain_group & domains_to_setup, timeout)
|
||||
(name, domain_group, timeout)
|
||||
for name, domain_group, timeout in STAGE_0_INTEGRATIONS
|
||||
),
|
||||
("stage 1", STAGE_1_INTEGRATIONS & domains_to_setup, STAGE_1_TIMEOUT),
|
||||
("1", STAGE_1_INTEGRATIONS, STAGE_1_TIMEOUT),
|
||||
("2", domains, STAGE_2_TIMEOUT),
|
||||
]
|
||||
|
||||
_LOGGER.info("Setting up stage 0 and 1")
|
||||
for name, domain_group, timeout in stage_0_and_1_domains:
|
||||
if not domain_group:
|
||||
_LOGGER.info("Setting up stage 0")
|
||||
for name, domain_group, timeout in stages:
|
||||
stage_domains_unfiltered = domain_group & all_domains
|
||||
if not stage_domains_unfiltered:
|
||||
_LOGGER.info("Nothing to set up in stage %s: %s", name, domain_group)
|
||||
continue
|
||||
|
||||
_LOGGER.info("Setting up %s: %s", name, domain_group)
|
||||
to_be_loaded = domain_group.copy()
|
||||
to_be_loaded.update(
|
||||
stage_domains = stage_domains_unfiltered - hass.config.components
|
||||
if not stage_domains:
|
||||
_LOGGER.info("Already set up stage %s: %s", name, stage_domains_unfiltered)
|
||||
continue
|
||||
|
||||
stage_dep_domains_unfiltered = {
|
||||
dep
|
||||
for domain in domain_group
|
||||
if (integration := integration_cache.get(domain)) is not None
|
||||
for dep in integration.all_dependencies
|
||||
for domain in stage_domains
|
||||
for dep in integrations_after_dependencies[domain]
|
||||
if dep not in stage_domains
|
||||
}
|
||||
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
|
||||
|
||||
stage_all_domains = stage_domains | stage_dep_domains
|
||||
|
||||
_LOGGER.info(
|
||||
"Setting up stage %s: %s | %s\nDependencies: %s | %s",
|
||||
name,
|
||||
stage_domains,
|
||||
stage_domains_unfiltered - stage_domains,
|
||||
stage_dep_domains,
|
||||
stage_dep_domains_unfiltered - stage_dep_domains,
|
||||
)
|
||||
async_set_domains_to_be_loaded(hass, to_be_loaded)
|
||||
stage_2_domains -= to_be_loaded
|
||||
|
||||
if timeout is None:
|
||||
await _async_setup_multi_components(hass, domain_group, config)
|
||||
else:
|
||||
try:
|
||||
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
|
||||
await _async_setup_multi_components(hass, domain_group, config)
|
||||
except TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Setup timed out for %s waiting on %s - moving forward",
|
||||
name,
|
||||
hass._active_tasks, # noqa: SLF001
|
||||
)
|
||||
|
||||
# Add after dependencies when setting up stage 2 domains
|
||||
async_set_domains_to_be_loaded(hass, stage_2_domains)
|
||||
|
||||
if stage_2_domains:
|
||||
_LOGGER.info("Setting up stage 2: %s", stage_2_domains)
|
||||
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||
continue
|
||||
try:
|
||||
async with hass.timeout.async_timeout(
|
||||
STAGE_2_TIMEOUT, cool_down=COOLDOWN_TIME
|
||||
):
|
||||
await _async_setup_multi_components(hass, stage_2_domains, config)
|
||||
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
|
||||
await _async_setup_multi_components(hass, stage_all_domains, config)
|
||||
except TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Setup timed out for stage 2 waiting on %s - moving forward",
|
||||
"Setup timed out for stage %s waiting on %s - moving forward",
|
||||
name,
|
||||
hass._active_tasks, # noqa: SLF001
|
||||
)
|
||||
|
||||
@@ -1053,8 +1036,6 @@ async def _async_setup_multi_components(
|
||||
config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Set up multiple domains. Log on failure."""
|
||||
# Avoid creating tasks for domains that were setup in a previous stage
|
||||
domains_not_yet_setup = domains - hass.config.components
|
||||
# Create setup tasks for base platforms first since everything will have
|
||||
# to wait to be imported, and the sooner we can get the base platforms
|
||||
# loaded the sooner we can start loading the rest of the integrations.
|
||||
@@ -1064,9 +1045,7 @@ async def _async_setup_multi_components(
|
||||
f"setup component {domain}",
|
||||
eager_start=True,
|
||||
)
|
||||
for domain in sorted(
|
||||
domains_not_yet_setup, key=SETUP_ORDER_SORT_KEY, reverse=True
|
||||
)
|
||||
for domain in sorted(domains, key=SETUP_ORDER_SORT_KEY, reverse=True)
|
||||
}
|
||||
results = await asyncio.gather(*futures.values(), return_exceptions=True)
|
||||
for idx, domain in enumerate(futures):
|
||||
|
5
homeassistant/brands/bosch.json
Normal file
5
homeassistant/brands/bosch.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "bosch",
|
||||
"name": "Bosch",
|
||||
"integrations": ["bosch_alarm", "bosch_shc", "home_connect"]
|
||||
}
|
5
homeassistant/brands/eve.json
Normal file
5
homeassistant/brands/eve.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "eve",
|
||||
"name": "Eve",
|
||||
"iot_standards": ["matter"]
|
||||
}
|
@@ -6,6 +6,7 @@
|
||||
"google_assistant_sdk",
|
||||
"google_cloud",
|
||||
"google_drive",
|
||||
"google_gemini",
|
||||
"google_generative_ai_conversation",
|
||||
"google_mail",
|
||||
"google_maps",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"domain": "motionblinds",
|
||||
"name": "Motionblinds",
|
||||
"integrations": ["motion_blinds", "motionblinds_ble"]
|
||||
"integrations": ["motion_blinds", "motionblinds_ble"],
|
||||
"iot_standards": ["matter"]
|
||||
}
|
||||
|
6
homeassistant/brands/nuki.json
Normal file
6
homeassistant/brands/nuki.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"domain": "nuki",
|
||||
"name": "Nuki",
|
||||
"integrations": ["nuki"],
|
||||
"iot_standards": ["matter"]
|
||||
}
|
@@ -24,7 +24,7 @@ from homeassistant.components.weather import (
|
||||
|
||||
API_METRIC: Final = "Metric"
|
||||
ATTRIBUTION: Final = "Data provided by AccuWeather"
|
||||
ATTR_CATEGORY: Final = "Category"
|
||||
ATTR_CATEGORY_VALUE = "CategoryValue"
|
||||
ATTR_DIRECTION: Final = "Direction"
|
||||
ATTR_ENGLISH: Final = "English"
|
||||
ATTR_LEVEL: Final = "level"
|
||||
@@ -55,5 +55,18 @@ CONDITION_MAP = {
|
||||
for cond_ha, cond_codes in CONDITION_CLASSES.items()
|
||||
for cond_code in cond_codes
|
||||
}
|
||||
AIR_QUALITY_CATEGORY_MAP = {
|
||||
1: "good",
|
||||
2: "moderate",
|
||||
3: "unhealthy",
|
||||
4: "very_unhealthy",
|
||||
5: "hazardous",
|
||||
}
|
||||
POLLEN_CATEGORY_MAP = {
|
||||
1: "low",
|
||||
2: "moderate",
|
||||
3: "high",
|
||||
4: "very_high",
|
||||
}
|
||||
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
|
||||
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
|
||||
|
@@ -75,7 +75,11 @@ class AccuWeatherObservationDataUpdateCoordinator(
|
||||
async with timeout(10):
|
||||
result = await self.accuweather.async_get_current_conditions()
|
||||
except EXCEPTIONS as error:
|
||||
raise UpdateFailed(error) from error
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="current_conditions_update_error",
|
||||
translation_placeholders={"error": repr(error)},
|
||||
) from error
|
||||
|
||||
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
||||
|
||||
@@ -117,9 +121,15 @@ class AccuWeatherDailyForecastDataUpdateCoordinator(
|
||||
"""Update data via library."""
|
||||
try:
|
||||
async with timeout(10):
|
||||
result = await self.accuweather.async_get_daily_forecast()
|
||||
result = await self.accuweather.async_get_daily_forecast(
|
||||
language=self.hass.config.language
|
||||
)
|
||||
except EXCEPTIONS as error:
|
||||
raise UpdateFailed(error) from error
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="forecast_update_error",
|
||||
translation_placeholders={"error": repr(error)},
|
||||
) from error
|
||||
|
||||
_LOGGER.debug("Requests remaining: %d", self.accuweather.requests_remaining)
|
||||
|
||||
|
@@ -7,6 +7,6 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["accuweather"],
|
||||
"requirements": ["accuweather==4.1.0"],
|
||||
"requirements": ["accuweather==4.2.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
@@ -29,8 +29,9 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import (
|
||||
AIR_QUALITY_CATEGORY_MAP,
|
||||
API_METRIC,
|
||||
ATTR_CATEGORY,
|
||||
ATTR_CATEGORY_VALUE,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_ENGLISH,
|
||||
ATTR_LEVEL,
|
||||
@@ -38,6 +39,7 @@ from .const import (
|
||||
ATTR_VALUE,
|
||||
ATTRIBUTION,
|
||||
MAX_FORECAST_DAYS,
|
||||
POLLEN_CATEGORY_MAP,
|
||||
)
|
||||
from .coordinator import (
|
||||
AccuWeatherConfigEntry,
|
||||
@@ -59,9 +61,9 @@ class AccuWeatherSensorDescription(SensorEntityDescription):
|
||||
FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
AccuWeatherSensorDescription(
|
||||
key="AirQuality",
|
||||
value_fn=lambda data: cast(str, data[ATTR_CATEGORY]),
|
||||
value_fn=lambda data: AIR_QUALITY_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]],
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["good", "hazardous", "high", "low", "moderate", "unhealthy"],
|
||||
options=list(AIR_QUALITY_CATEGORY_MAP.values()),
|
||||
translation_key="air_quality",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
@@ -83,7 +85,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
attr_fn=lambda data: {
|
||||
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||
},
|
||||
translation_key="grass_pollen",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
@@ -107,7 +111,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
attr_fn=lambda data: {
|
||||
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||
},
|
||||
translation_key="mold_pollen",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
@@ -115,7 +121,9 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
attr_fn=lambda data: {
|
||||
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||
},
|
||||
translation_key="ragweed_pollen",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
@@ -181,14 +189,18 @@ FORECAST_SENSOR_TYPES: tuple[AccuWeatherSensorDescription, ...] = (
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
attr_fn=lambda data: {
|
||||
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||
},
|
||||
translation_key="tree_pollen",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
key="UVIndex",
|
||||
native_unit_of_measurement=UV_INDEX,
|
||||
value_fn=lambda data: cast(int, data[ATTR_VALUE]),
|
||||
attr_fn=lambda data: {ATTR_LEVEL: data[ATTR_CATEGORY]},
|
||||
attr_fn=lambda data: {
|
||||
ATTR_LEVEL: POLLEN_CATEGORY_MAP[data[ATTR_CATEGORY_VALUE]]
|
||||
},
|
||||
translation_key="uv_index_forecast",
|
||||
),
|
||||
AccuWeatherSensorDescription(
|
||||
|
@@ -26,10 +26,20 @@
|
||||
"state": {
|
||||
"good": "Good",
|
||||
"hazardous": "Hazardous",
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"moderate": "Moderate",
|
||||
"unhealthy": "Unhealthy"
|
||||
"unhealthy": "Unhealthy",
|
||||
"very_unhealthy": "Very unhealthy"
|
||||
},
|
||||
"state_attributes": {
|
||||
"options": {
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]",
|
||||
"very_unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::very_unhealthy%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"apparent_temperature": {
|
||||
@@ -62,12 +72,10 @@
|
||||
"level": {
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "Moderate",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,12 +89,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +106,15 @@
|
||||
"steady": "Steady",
|
||||
"rising": "Rising",
|
||||
"falling": "Falling"
|
||||
},
|
||||
"state_attributes": {
|
||||
"options": {
|
||||
"state": {
|
||||
"falling": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::falling%]",
|
||||
"rising": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::rising%]",
|
||||
"steady": "[%key:component::accuweather::entity::sensor::pressure_tendency::state::steady%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ragweed_pollen": {
|
||||
@@ -108,12 +123,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,12 +167,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,12 +181,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,12 +195,10 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"good": "[%key:component::accuweather::entity::sensor::air_quality::state::good%]",
|
||||
"hazardous": "[%key:component::accuweather::entity::sensor::air_quality::state::hazardous%]",
|
||||
"high": "[%key:component::accuweather::entity::sensor::air_quality::state::high%]",
|
||||
"low": "[%key:component::accuweather::entity::sensor::air_quality::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::air_quality::state::moderate%]",
|
||||
"unhealthy": "[%key:component::accuweather::entity::sensor::air_quality::state::unhealthy%]"
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
"very_high": "[%key:common::state::very_high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +229,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"current_conditions_update_error": {
|
||||
"message": "An error occurred while retrieving weather current conditions data from the AccuWeather API: {error}"
|
||||
},
|
||||
"forecast_update_error": {
|
||||
"message": "An error occurred while retrieving weather forecast data from the AccuWeather API: {error}"
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
"info": {
|
||||
"can_reach_server": "Reach AccuWeather server",
|
||||
|
@@ -2,25 +2,38 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONNECTION_TYPE, LOCAL
|
||||
from .coordinator import AdaxCloudCoordinator, AdaxConfigEntry, AdaxLocalCoordinator
|
||||
|
||||
PLATFORMS = [Platform.CLIMATE]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:
|
||||
"""Set up Adax from a config entry."""
|
||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||
local_coordinator = AdaxLocalCoordinator(hass, entry)
|
||||
entry.runtime_data = local_coordinator
|
||||
else:
|
||||
cloud_coordinator = AdaxCloudCoordinator(hass, entry)
|
||||
entry.runtime_data = cloud_coordinator
|
||||
|
||||
await entry.runtime_data.async_config_entry_first_refresh()
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: AdaxConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old entry."""
|
||||
# convert title and unique_id to string
|
||||
if config_entry.version == 1:
|
||||
|
@@ -12,57 +12,42 @@ from homeassistant.components.climate import (
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_PASSWORD,
|
||||
CONF_TOKEN,
|
||||
CONF_UNIQUE_ID,
|
||||
PRECISION_WHOLE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ACCOUNT_ID, CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
from . import AdaxConfigEntry
|
||||
from .const import CONNECTION_TYPE, DOMAIN, LOCAL
|
||||
from .coordinator import AdaxCloudCoordinator, AdaxLocalCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: AdaxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Adax thermostat with config flow."""
|
||||
if entry.data.get(CONNECTION_TYPE) == LOCAL:
|
||||
adax_data_handler = AdaxLocal(
|
||||
entry.data[CONF_IP_ADDRESS],
|
||||
entry.data[CONF_TOKEN],
|
||||
websession=async_get_clientsession(hass, verify_ssl=False),
|
||||
)
|
||||
local_coordinator = cast(AdaxLocalCoordinator, entry.runtime_data)
|
||||
async_add_entities(
|
||||
[LocalAdaxDevice(adax_data_handler, entry.data[CONF_UNIQUE_ID])], True
|
||||
[LocalAdaxDevice(local_coordinator, entry.data[CONF_UNIQUE_ID])],
|
||||
)
|
||||
else:
|
||||
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
|
||||
async_add_entities(
|
||||
AdaxDevice(cloud_coordinator, device_id)
|
||||
for device_id in cloud_coordinator.data
|
||||
)
|
||||
return
|
||||
|
||||
adax_data_handler = Adax(
|
||||
entry.data[ACCOUNT_ID],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
(
|
||||
AdaxDevice(room, adax_data_handler)
|
||||
for room in await adax_data_handler.get_rooms()
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class AdaxDevice(ClimateEntity):
|
||||
class AdaxDevice(CoordinatorEntity[AdaxCloudCoordinator], ClimateEntity):
|
||||
"""Representation of a heater."""
|
||||
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
@@ -76,20 +61,37 @@ class AdaxDevice(ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AdaxCloudCoordinator,
|
||||
device_id: str,
|
||||
) -> None:
|
||||
"""Initialize the heater."""
|
||||
self._device_id = heater_data["id"]
|
||||
self._adax_data_handler = adax_data_handler
|
||||
super().__init__(coordinator)
|
||||
self._adax_data_handler: Adax = coordinator.adax_data_handler
|
||||
self._device_id = device_id
|
||||
|
||||
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
|
||||
self._attr_name = self.room["name"]
|
||||
self._attr_unique_id = f"{self.room['homeId']}_{self._device_id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, heater_data["id"])},
|
||||
identifiers={(DOMAIN, device_id)},
|
||||
# Instead of setting the device name to the entity name, adax
|
||||
# should be updated to set has_entity_name = True, and set the entity
|
||||
# name to None
|
||||
name=cast(str | None, self.name),
|
||||
manufacturer="Adax",
|
||||
)
|
||||
self._apply_data(self.room)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Whether the entity is available or not."""
|
||||
return super().available and self._device_id in self.coordinator.data
|
||||
|
||||
@property
|
||||
def room(self) -> dict[str, Any]:
|
||||
"""Gets the data for this particular device."""
|
||||
return self.coordinator.data[self._device_id]
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set hvac mode."""
|
||||
@@ -104,7 +106,9 @@ class AdaxDevice(ClimateEntity):
|
||||
)
|
||||
else:
|
||||
return
|
||||
await self._adax_data_handler.update()
|
||||
|
||||
# Request data refresh from source to verify that update was successful
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
@@ -114,28 +118,31 @@ class AdaxDevice(ClimateEntity):
|
||||
self._device_id, temperature, True
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
for room in await self._adax_data_handler.get_rooms():
|
||||
if room["id"] != self._device_id:
|
||||
continue
|
||||
self._attr_name = room["name"]
|
||||
self._attr_current_temperature = room.get("temperature")
|
||||
self._attr_target_temperature = room.get("targetTemperature")
|
||||
if room["heatingEnabled"]:
|
||||
self._attr_hvac_mode = HVACMode.HEAT
|
||||
self._attr_icon = "mdi:radiator"
|
||||
else:
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
return
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
if room := self.room:
|
||||
self._apply_data(room)
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
def _apply_data(self, room: dict[str, Any]) -> None:
|
||||
"""Update the appropriate attributues based on received data."""
|
||||
self._attr_current_temperature = room.get("temperature")
|
||||
self._attr_target_temperature = room.get("targetTemperature")
|
||||
if room["heatingEnabled"]:
|
||||
self._attr_hvac_mode = HVACMode.HEAT
|
||||
self._attr_icon = "mdi:radiator"
|
||||
else:
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
|
||||
|
||||
class LocalAdaxDevice(ClimateEntity):
|
||||
class LocalAdaxDevice(CoordinatorEntity[AdaxLocalCoordinator], ClimateEntity):
|
||||
"""Representation of a heater."""
|
||||
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_hvac_mode = HVACMode.HEAT
|
||||
_attr_hvac_mode = HVACMode.OFF
|
||||
_attr_icon = "mdi:radiator-off"
|
||||
_attr_max_temp = 35
|
||||
_attr_min_temp = 5
|
||||
_attr_supported_features = (
|
||||
@@ -146,9 +153,10 @@ class LocalAdaxDevice(ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
|
||||
def __init__(self, adax_data_handler: AdaxLocal, unique_id: str) -> None:
|
||||
def __init__(self, coordinator: AdaxLocalCoordinator, unique_id: str) -> None:
|
||||
"""Initialize the heater."""
|
||||
self._adax_data_handler = adax_data_handler
|
||||
super().__init__(coordinator)
|
||||
self._adax_data_handler: AdaxLocal = coordinator.adax_data_handler
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, unique_id)},
|
||||
@@ -169,17 +177,20 @@ class LocalAdaxDevice(ClimateEntity):
|
||||
return
|
||||
await self._adax_data_handler.set_target_temperature(temperature)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
data = await self._adax_data_handler.get_status()
|
||||
self._attr_current_temperature = data["current_temperature"]
|
||||
self._attr_available = self._attr_current_temperature is not None
|
||||
if (target_temp := data["target_temperature"]) == 0:
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
if target_temp == 0:
|
||||
self._attr_target_temperature = self._attr_min_temp
|
||||
else:
|
||||
self._attr_hvac_mode = HVACMode.HEAT
|
||||
self._attr_icon = "mdi:radiator"
|
||||
self._attr_target_temperature = target_temp
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
if data := self.coordinator.data:
|
||||
self._attr_current_temperature = data["current_temperature"]
|
||||
self._attr_available = self._attr_current_temperature is not None
|
||||
if (target_temp := data["target_temperature"]) == 0:
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_icon = "mdi:radiator-off"
|
||||
if target_temp == 0:
|
||||
self._attr_target_temperature = self._attr_min_temp
|
||||
else:
|
||||
self._attr_hvac_mode = HVACMode.HEAT
|
||||
self._attr_icon = "mdi:radiator"
|
||||
self._attr_target_temperature = target_temp
|
||||
|
||||
super()._handle_coordinator_update()
|
||||
|
@@ -1,5 +1,6 @@
|
||||
"""Constants for the Adax integration."""
|
||||
|
||||
import datetime
|
||||
from typing import Final
|
||||
|
||||
ACCOUNT_ID: Final = "account_id"
|
||||
@@ -9,3 +10,5 @@ DOMAIN: Final = "adax"
|
||||
LOCAL = "Local"
|
||||
WIFI_SSID = "wifi_ssid"
|
||||
WIFI_PSWD = "wifi_pswd"
|
||||
|
||||
SCAN_INTERVAL = datetime.timedelta(seconds=60)
|
||||
|
71
homeassistant/components/adax/coordinator.py
Normal file
71
homeassistant/components/adax/coordinator.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""DataUpdateCoordinator for the Adax component."""
|
||||
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from adax import Adax
|
||||
from adax_local import Adax as AdaxLocal
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import ACCOUNT_ID, SCAN_INTERVAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
type AdaxConfigEntry = ConfigEntry[AdaxCloudCoordinator | AdaxLocalCoordinator]
|
||||
|
||||
|
||||
class AdaxCloudCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
|
||||
"""Coordinator for updating data to and from Adax (cloud)."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: AdaxConfigEntry) -> None:
|
||||
"""Initialize the Adax coordinator used for Cloud mode."""
|
||||
super().__init__(
|
||||
hass,
|
||||
config_entry=entry,
|
||||
logger=_LOGGER,
|
||||
name="AdaxCloud",
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
self.adax_data_handler = Adax(
|
||||
entry.data[ACCOUNT_ID],
|
||||
entry.data[CONF_PASSWORD],
|
||||
websession=async_get_clientsession(hass),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
|
||||
"""Fetch data from the Adax."""
|
||||
rooms = await self.adax_data_handler.get_rooms() or []
|
||||
return {r["id"]: r for r in rooms}
|
||||
|
||||
|
||||
class AdaxLocalCoordinator(DataUpdateCoordinator[dict[str, Any] | None]):
|
||||
"""Coordinator for updating data to and from Adax (local)."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: AdaxConfigEntry) -> None:
|
||||
"""Initialize the Adax coordinator used for Local mode."""
|
||||
super().__init__(
|
||||
hass,
|
||||
config_entry=entry,
|
||||
logger=_LOGGER,
|
||||
name="AdaxLocal",
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
self.adax_data_handler = AdaxLocal(
|
||||
entry.data[CONF_IP_ADDRESS],
|
||||
entry.data[CONF_TOKEN],
|
||||
websession=async_get_clientsession(hass, verify_ssl=False),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Fetch data from the Adax."""
|
||||
if result := await self.adax_data_handler.get_status():
|
||||
return cast(dict[str, Any], result)
|
||||
raise UpdateFailed("Got invalid status from device")
|
@@ -5,14 +5,14 @@
|
||||
"data": {
|
||||
"connection_type": "Select connection type"
|
||||
},
|
||||
"description": "Select connection type. Local requires heaters with bluetooth"
|
||||
"description": "Select connection type. Local requires heaters with Bluetooth"
|
||||
},
|
||||
"local": {
|
||||
"data": {
|
||||
"wifi_ssid": "Wi-Fi SSID",
|
||||
"wifi_pswd": "Wi-Fi Password"
|
||||
"wifi_pswd": "Wi-Fi password"
|
||||
},
|
||||
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue led starts blinking before pressing Submit. Configuring heater might take some minutes."
|
||||
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue LED starts blinking before pressing Submit. Configuring heater might take some minutes."
|
||||
},
|
||||
"cloud": {
|
||||
"data": {
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -14,6 +15,7 @@ from homeassistant.components.climate import (
|
||||
FAN_MEDIUM,
|
||||
ClimateEntity,
|
||||
ClimateEntityFeature,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
|
||||
@@ -49,6 +51,14 @@ ADVANTAGE_AIR_MYTEMP_ENABLED = "climateControlModeEnabled"
|
||||
ADVANTAGE_AIR_HEAT_TARGET = "myAutoHeatTargetTemp"
|
||||
ADVANTAGE_AIR_COOL_TARGET = "myAutoCoolTargetTemp"
|
||||
ADVANTAGE_AIR_MYFAN = "autoAA"
|
||||
ADVANTAGE_AIR_MYAUTO_MODE_SET = "myAutoModeCurrentSetMode"
|
||||
|
||||
HVAC_ACTIONS = {
|
||||
"cool": HVACAction.COOLING,
|
||||
"heat": HVACAction.HEATING,
|
||||
"vent": HVACAction.FAN,
|
||||
"dry": HVACAction.DRYING,
|
||||
}
|
||||
|
||||
HVAC_MODES = [
|
||||
HVACMode.OFF,
|
||||
@@ -175,6 +185,17 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
return ADVANTAGE_AIR_HVAC_MODES.get(self._ac["mode"])
|
||||
return HVACMode.OFF
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the current running HVAC action."""
|
||||
if self._ac["state"] == ADVANTAGE_AIR_STATE_OFF:
|
||||
return HVACAction.OFF
|
||||
if self._ac["mode"] == "myauto":
|
||||
return HVAC_ACTIONS.get(
|
||||
self._ac.get(ADVANTAGE_AIR_MYAUTO_MODE_SET, HVACAction.OFF)
|
||||
)
|
||||
return HVAC_ACTIONS.get(self._ac["mode"])
|
||||
|
||||
@property
|
||||
def fan_mode(self) -> str | None:
|
||||
"""Return the current fan modes."""
|
||||
@@ -273,6 +294,22 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
||||
return HVACMode.HEAT_COOL
|
||||
return HVACMode.OFF
|
||||
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the HVAC action, inheriting from master AC if zone is open but idle if air is <= 5%."""
|
||||
if self._ac["state"] == ADVANTAGE_AIR_STATE_OFF:
|
||||
return HVACAction.OFF
|
||||
master_action = HVAC_ACTIONS.get(self._ac["mode"], HVACAction.OFF)
|
||||
if self._ac["mode"] == "myauto":
|
||||
master_action = HVAC_ACTIONS.get(
|
||||
str(self._ac.get(ADVANTAGE_AIR_MYAUTO_MODE_SET)), HVACAction.OFF
|
||||
)
|
||||
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
|
||||
if self._zone["value"] <= Decimal(5):
|
||||
return HVACAction.IDLE
|
||||
return master_action
|
||||
return HVACAction.OFF
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Return the current temperature."""
|
||||
|
@@ -7,3 +7,4 @@ ADVANTAGE_AIR_STATE_CLOSE = "close"
|
||||
ADVANTAGE_AIR_STATE_ON = "on"
|
||||
ADVANTAGE_AIR_STATE_OFF = "off"
|
||||
ADVANTAGE_AIR_AUTOFAN_ENABLED = "aaAutoFanModeEnabled"
|
||||
ADVANTAGE_AIR_NIGHT_MODE_ENABLED = "quietNightModeEnabled"
|
||||
|
@@ -41,7 +41,7 @@ async def async_setup_entry(
|
||||
entities.append(
|
||||
AdvantageAirThingCover(instance, thing, CoverDeviceClass.BLIND)
|
||||
)
|
||||
elif thing["channelDipState"] == 3: # 3 = "Garage door"
|
||||
elif thing["channelDipState"] in [3, 10]: # 3 & 10 = "Garage door"
|
||||
entities.append(
|
||||
AdvantageAirThingCover(instance, thing, CoverDeviceClass.GARAGE)
|
||||
)
|
||||
|
@@ -9,6 +9,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from . import AdvantageAirDataConfigEntry
|
||||
from .const import (
|
||||
ADVANTAGE_AIR_AUTOFAN_ENABLED,
|
||||
ADVANTAGE_AIR_NIGHT_MODE_ENABLED,
|
||||
ADVANTAGE_AIR_STATE_OFF,
|
||||
ADVANTAGE_AIR_STATE_ON,
|
||||
)
|
||||
@@ -32,6 +33,8 @@ async def async_setup_entry(
|
||||
entities.append(AdvantageAirFreshAir(instance, ac_key))
|
||||
if ADVANTAGE_AIR_AUTOFAN_ENABLED in ac_device["info"]:
|
||||
entities.append(AdvantageAirMyFan(instance, ac_key))
|
||||
if ADVANTAGE_AIR_NIGHT_MODE_ENABLED in ac_device["info"]:
|
||||
entities.append(AdvantageAirNightMode(instance, ac_key))
|
||||
if things := instance.coordinator.data.get("myThings"):
|
||||
entities.extend(
|
||||
AdvantageAirRelay(instance, thing)
|
||||
@@ -93,6 +96,32 @@ class AdvantageAirMyFan(AdvantageAirAcEntity, SwitchEntity):
|
||||
await self.async_update_ac({ADVANTAGE_AIR_AUTOFAN_ENABLED: False})
|
||||
|
||||
|
||||
class AdvantageAirNightMode(AdvantageAirAcEntity, SwitchEntity):
|
||||
"""Representation of Advantage 'MySleep$aver' Mode control."""
|
||||
|
||||
_attr_icon = "mdi:weather-night"
|
||||
_attr_name = "MySleep$aver"
|
||||
_attr_device_class = SwitchDeviceClass.SWITCH
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||
"""Initialize an Advantage Air Night Mode control."""
|
||||
super().__init__(instance, ac_key)
|
||||
self._attr_unique_id += "-nightmode"
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the Night Mode status."""
|
||||
return self._ac[ADVANTAGE_AIR_NIGHT_MODE_ENABLED]
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn Night Mode on."""
|
||||
await self.async_update_ac({ADVANTAGE_AIR_NIGHT_MODE_ENABLED: True})
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn Night Mode off."""
|
||||
await self.async_update_ac({ADVANTAGE_AIR_NIGHT_MODE_ENABLED: False})
|
||||
|
||||
|
||||
class AdvantageAirRelay(AdvantageAirThingEntity, SwitchEntity):
|
||||
"""Representation of Advantage Air Thing."""
|
||||
|
||||
|
@@ -51,7 +51,7 @@
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_cannot_connect": {
|
||||
"title": "The {integration_title} YAML configuration import failed",
|
||||
"description": "Configuring {integration_title} using YAML is being removed but there was an connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
"description": "Configuring {integration_title} using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@
|
||||
}
|
||||
},
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to setup {model}?"
|
||||
"description": "Do you want to set up {model}?"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
@@ -68,8 +68,8 @@
|
||||
"led_bar_mode": {
|
||||
"name": "LED bar mode",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"co2": "Carbon dioxide",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
||||
"pm": "Particulate matter"
|
||||
}
|
||||
},
|
||||
@@ -143,8 +143,8 @@
|
||||
"led_bar_mode": {
|
||||
"name": "[%key:component::airgradient::entity::select::led_bar_mode::name%]",
|
||||
"state": {
|
||||
"off": "[%key:component::airgradient::entity::select::led_bar_mode::state::off%]",
|
||||
"co2": "[%key:component::airgradient::entity::select::led_bar_mode::state::co2%]",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
||||
"pm": "[%key:component::airgradient::entity::select::led_bar_mode::state::pm%]"
|
||||
}
|
||||
},
|
||||
|
@@ -105,7 +105,14 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | i
|
||||
try:
|
||||
await measurements.update()
|
||||
except (AirlyError, ClientConnectorError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_error",
|
||||
translation_placeholders={
|
||||
"entry": self.config_entry.title,
|
||||
"error": repr(error),
|
||||
},
|
||||
) from error
|
||||
|
||||
_LOGGER.debug(
|
||||
"Requests remaining: %s/%s",
|
||||
@@ -126,7 +133,11 @@ class AirlyDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str | float | i
|
||||
standards = measurements.current["standards"]
|
||||
|
||||
if index["description"] == NO_AIRLY_SENSORS:
|
||||
raise UpdateFailed("Can't retrieve data: no Airly sensors in this area")
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_station",
|
||||
translation_placeholders={"entry": self.config_entry.title},
|
||||
)
|
||||
for value in values:
|
||||
data[value["name"]] = value["value"]
|
||||
for standard in standards:
|
||||
|
@@ -36,5 +36,13 @@
|
||||
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"update_error": {
|
||||
"message": "An error occurred while retrieving data from the Airly API for {entry}: {error}"
|
||||
},
|
||||
"no_station": {
|
||||
"message": "An error occurred while retrieving data from the Airly API for {entry}: no measuring stations in this area"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from pyairnow import WebServiceAPI
|
||||
from pyairnow.conv import aqi_to_concentration
|
||||
from pyairnow.errors import AirNowError
|
||||
from pyairnow.errors import AirNowError, InvalidJsonError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -79,7 +79,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
distance=self.distance,
|
||||
)
|
||||
|
||||
except (AirNowError, ClientConnectorError) as error:
|
||||
except (AirNowError, ClientConnectorError, InvalidJsonError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
if not obs:
|
||||
|
@@ -7,7 +7,7 @@
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||
"radius": "Station Radius (miles; optional)"
|
||||
"radius": "Station radius (miles; optional)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -25,7 +25,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"radius": "Station Radius (miles)"
|
||||
"radius": "Station radius (miles)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -91,7 +91,7 @@
|
||||
"name": "Hydrogen fluoride"
|
||||
},
|
||||
"health_index": {
|
||||
"name": "Health Index"
|
||||
"name": "Health index"
|
||||
},
|
||||
"absolute_humidity": {
|
||||
"name": "Absolute humidity"
|
||||
@@ -112,10 +112,10 @@
|
||||
"name": "Oxygen"
|
||||
},
|
||||
"performance_index": {
|
||||
"name": "Performance Index"
|
||||
"name": "Performance index"
|
||||
},
|
||||
"hydrogen_phosphide": {
|
||||
"name": "Hydrogen Phosphide"
|
||||
"name": "Hydrogen phosphide"
|
||||
},
|
||||
"relative_pressure": {
|
||||
"name": "Relative pressure"
|
||||
@@ -127,22 +127,22 @@
|
||||
"name": "Refrigerant"
|
||||
},
|
||||
"silicon_hydride": {
|
||||
"name": "Silicon Hydride"
|
||||
"name": "Silicon hydride"
|
||||
},
|
||||
"noise": {
|
||||
"name": "Noise"
|
||||
},
|
||||
"maximum_noise": {
|
||||
"name": "Noise (Maximum)"
|
||||
"name": "Noise (maximum)"
|
||||
},
|
||||
"radon": {
|
||||
"name": "Radon"
|
||||
},
|
||||
"industrial_volatile_organic_compounds": {
|
||||
"name": "VOCs (Industrial)"
|
||||
"name": "VOCs (industrial)"
|
||||
},
|
||||
"virus_index": {
|
||||
"name": "Virus Index"
|
||||
"name": "Virus index"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -102,7 +102,8 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
device = await self._get_device_data(discovery_info)
|
||||
except AirthingsDeviceUpdateError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
_LOGGER.exception("Unknown error occurred")
|
||||
return self.async_abort(reason="unknown")
|
||||
|
||||
name = get_name(device)
|
||||
@@ -160,7 +161,8 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
device = await self._get_device_data(discovery_info)
|
||||
except AirthingsDeviceUpdateError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
_LOGGER.exception("Unknown error occurred")
|
||||
return self.async_abort(reason="unknown")
|
||||
name = get_name(device)
|
||||
self._discovered_devices[address] = Discovery(name, discovery_info, device)
|
||||
|
@@ -32,7 +32,8 @@ class AirTouch5ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client = Airtouch5SimpleClient(user_input[CONF_HOST])
|
||||
try:
|
||||
await client.test_connection()
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors = {"base": "cannot_connect"}
|
||||
else:
|
||||
await self.async_set_unique_id(user_input[CONF_HOST])
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"geography_by_coords": {
|
||||
"title": "Configure a Geography",
|
||||
"title": "Configure a geography",
|
||||
"description": "Use the AirVisual cloud API to monitor a latitude/longitude.",
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
@@ -16,8 +16,8 @@
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"city": "City",
|
||||
"country": "Country",
|
||||
"state": "State"
|
||||
"state": "State",
|
||||
"country": "[%key:common::config_flow::data::country%]"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
@@ -56,12 +56,12 @@
|
||||
"sensor": {
|
||||
"pollutant_label": {
|
||||
"state": {
|
||||
"co": "Carbon Monoxide",
|
||||
"n2": "Nitrogen Dioxide",
|
||||
"o3": "Ozone",
|
||||
"p1": "PM10",
|
||||
"p2": "PM2.5",
|
||||
"s2": "Sulfur Dioxide"
|
||||
"co": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
|
||||
"n2": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]",
|
||||
"o3": "[%key:component::sensor::entity_component::ozone::name%]",
|
||||
"p1": "[%key:component::sensor::entity_component::pm10::name%]",
|
||||
"p2": "[%key:component::sensor::entity_component::pm25::name%]",
|
||||
"s2": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]"
|
||||
}
|
||||
},
|
||||
"pollutant_level": {
|
||||
|
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.9.9"]
|
||||
"requirements": ["aioairzone==1.0.0"]
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ from aioairzone.const import (
|
||||
AZD_HUMIDITY,
|
||||
AZD_TEMP,
|
||||
AZD_TEMP_UNIT,
|
||||
AZD_THERMOSTAT_BATTERY,
|
||||
AZD_THERMOSTAT_SIGNAL,
|
||||
AZD_WEBSERVER,
|
||||
AZD_WIFI_RSSI,
|
||||
AZD_ZONES,
|
||||
@@ -73,6 +75,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
key=AZD_THERMOSTAT_BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_THERMOSTAT_SIGNAL,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="thermostat_signal",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@@ -76,6 +76,9 @@
|
||||
"sensor": {
|
||||
"rssi": {
|
||||
"name": "RSSI"
|
||||
},
|
||||
"thermostat_signal": {
|
||||
"name": "Signal strength"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.6.10"]
|
||||
"requirements": ["aioairzone-cloud==0.6.12"]
|
||||
}
|
||||
|
@@ -32,9 +32,9 @@
|
||||
"air_quality": {
|
||||
"name": "Air Quality mode",
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"auto": "Auto"
|
||||
"off": "[%key:common::state::off%]",
|
||||
"on": "[%key:common::state::on%]",
|
||||
"auto": "[%key:common::state::auto%]"
|
||||
}
|
||||
},
|
||||
"modes": {
|
||||
|
@@ -1438,7 +1438,7 @@ class AlexaModeController(AlexaCapability):
|
||||
# Fan preset_mode
|
||||
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
|
||||
mode = self.entity.attributes.get(fan.ATTR_PRESET_MODE, None)
|
||||
if mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, None):
|
||||
if mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, ()):
|
||||
return f"{fan.ATTR_PRESET_MODE}.{mode}"
|
||||
|
||||
# Humidifier mode
|
||||
|
@@ -719,7 +719,7 @@ class LockCapabilities(AlexaEntity):
|
||||
yield Alexa(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(media_player.const.DOMAIN)
|
||||
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
|
||||
class MediaPlayerCapabilities(AlexaEntity):
|
||||
"""Class to represent MediaPlayer capabilities."""
|
||||
|
||||
@@ -757,9 +757,7 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||
|
||||
if supported & media_player.MediaPlayerEntityFeature.SELECT_SOURCE:
|
||||
inputs = AlexaInputController.get_valid_inputs(
|
||||
self.entity.attributes.get(
|
||||
media_player.const.ATTR_INPUT_SOURCE_LIST, []
|
||||
)
|
||||
self.entity.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST, [])
|
||||
)
|
||||
if len(inputs) > 0:
|
||||
yield AlexaInputController(self.entity)
|
||||
@@ -776,8 +774,7 @@ class MediaPlayerCapabilities(AlexaEntity):
|
||||
and domain != "denonavr"
|
||||
):
|
||||
inputs = AlexaEqualizerController.get_valid_inputs(
|
||||
self.entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
|
||||
or []
|
||||
self.entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST) or []
|
||||
)
|
||||
if len(inputs) > 0:
|
||||
yield AlexaEqualizerController(self.entity)
|
||||
|
@@ -566,7 +566,7 @@ async def async_api_set_volume(
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume,
|
||||
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -589,7 +589,7 @@ async def async_api_select_input(
|
||||
|
||||
# Attempt to map the ALL UPPERCASE payload name to a source.
|
||||
# Strips trailing 1 to match single input devices.
|
||||
source_list = entity.attributes.get(media_player.const.ATTR_INPUT_SOURCE_LIST) or []
|
||||
source_list = entity.attributes.get(media_player.ATTR_INPUT_SOURCE_LIST) or []
|
||||
for source in source_list:
|
||||
formatted_source = (
|
||||
source.lower().replace("-", "").replace("_", "").replace(" ", "")
|
||||
@@ -611,7 +611,7 @@ async def async_api_select_input(
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_INPUT_SOURCE: media_input,
|
||||
media_player.ATTR_INPUT_SOURCE: media_input,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -636,7 +636,7 @@ async def async_api_adjust_volume(
|
||||
volume_delta = int(directive.payload["volume"])
|
||||
|
||||
entity = directive.entity
|
||||
current_level = entity.attributes[media_player.const.ATTR_MEDIA_VOLUME_LEVEL]
|
||||
current_level = entity.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL]
|
||||
|
||||
# read current state
|
||||
try:
|
||||
@@ -648,7 +648,7 @@ async def async_api_adjust_volume(
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_MEDIA_VOLUME_LEVEL: volume,
|
||||
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -709,7 +709,7 @@ async def async_api_set_mute(
|
||||
entity = directive.entity
|
||||
data: dict[str, Any] = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_MEDIA_VOLUME_MUTED: mute,
|
||||
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
@@ -1708,15 +1708,13 @@ async def async_api_changechannel(
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_ENTITY_ID: entity.entity_id,
|
||||
media_player.const.ATTR_MEDIA_CONTENT_ID: channel,
|
||||
media_player.const.ATTR_MEDIA_CONTENT_TYPE: (
|
||||
media_player.const.MEDIA_TYPE_CHANNEL
|
||||
),
|
||||
media_player.ATTR_MEDIA_CONTENT_ID: channel,
|
||||
media_player.ATTR_MEDIA_CONTENT_TYPE: (media_player.MediaType.CHANNEL),
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
entity.domain,
|
||||
media_player.const.SERVICE_PLAY_MEDIA,
|
||||
media_player.SERVICE_PLAY_MEDIA,
|
||||
data,
|
||||
blocking=False,
|
||||
context=context,
|
||||
@@ -1825,13 +1823,13 @@ async def async_api_set_eq_mode(
|
||||
context: ha.Context,
|
||||
) -> AlexaResponse:
|
||||
"""Process a SetMode request for EqualizerController."""
|
||||
mode = directive.payload["mode"]
|
||||
mode: str = directive.payload["mode"]
|
||||
entity = directive.entity
|
||||
data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
|
||||
sound_mode_list = entity.attributes.get(media_player.const.ATTR_SOUND_MODE_LIST)
|
||||
sound_mode_list = entity.attributes.get(media_player.ATTR_SOUND_MODE_LIST)
|
||||
if sound_mode_list and mode.lower() in sound_mode_list:
|
||||
data[media_player.const.ATTR_SOUND_MODE] = mode.lower()
|
||||
data[media_player.ATTR_SOUND_MODE] = mode.lower()
|
||||
else:
|
||||
msg = f"failed to map sound mode {mode} to a mode on {entity.entity_id}"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
|
@@ -3,10 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from collections.abc import Mapping
|
||||
from http import HTTPStatus
|
||||
import json
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -260,10 +260,10 @@ async def async_enable_proactive_mode(
|
||||
def extra_significant_check(
|
||||
hass: HomeAssistant,
|
||||
old_state: str,
|
||||
old_attrs: dict[Any, Any] | MappingProxyType[Any, Any],
|
||||
old_attrs: Mapping[Any, Any],
|
||||
old_extra_arg: Any,
|
||||
new_state: str,
|
||||
new_attrs: dict[str, Any] | MappingProxyType[Any, Any],
|
||||
new_attrs: Mapping[Any, Any],
|
||||
new_extra_arg: Any,
|
||||
) -> bool:
|
||||
"""Check if the serialized data has changed."""
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["boto3", "botocore", "s3transfer"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["boto3==1.34.131"]
|
||||
"requirements": ["boto3==1.37.1"]
|
||||
}
|
||||
|
@@ -240,6 +240,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
suggested_display_precision=0,
|
||||
entity_registry_enabled_default=False,
|
||||
device_class=SensorDeviceClass.WIND_DIRECTION,
|
||||
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_WINDGUSTMPH,
|
||||
|
@@ -609,6 +609,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
translation_key="wind_direction",
|
||||
native_unit_of_measurement=DEGREE,
|
||||
device_class=SensorDeviceClass.WIND_DIRECTION,
|
||||
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_WINDDIR_AVG10M,
|
||||
|
@@ -8,7 +8,7 @@ from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsClient,
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
)
|
||||
from python_homeassistant_analytics.models import IntegrationType
|
||||
from python_homeassistant_analytics.models import Environment, IntegrationType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
@@ -81,7 +81,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
try:
|
||||
addons = await client.get_addons()
|
||||
integrations = await client.get_integrations()
|
||||
integrations = await client.get_integrations(Environment.NEXT)
|
||||
custom_integrations = await client.get_custom_integrations()
|
||||
except HomeassistantAnalyticsConnectionError:
|
||||
LOGGER.exception("Error connecting to Home Assistant analytics")
|
||||
@@ -165,7 +165,7 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlow):
|
||||
)
|
||||
try:
|
||||
addons = await client.get_addons()
|
||||
integrations = await client.get_integrations()
|
||||
integrations = await client.get_integrations(Environment.NEXT)
|
||||
custom_integrations = await client.get_custom_integrations()
|
||||
except HomeassistantAnalyticsConnectionError:
|
||||
LOGGER.exception("Error connecting to Home Assistant analytics")
|
||||
|
@@ -3,12 +3,12 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"tracked_addons": "Addons",
|
||||
"tracked_addons": "Add-ons",
|
||||
"tracked_integrations": "Integrations",
|
||||
"tracked_custom_integrations": "Custom integrations"
|
||||
},
|
||||
"data_description": {
|
||||
"tracked_addons": "Select the addons you want to track",
|
||||
"tracked_addons": "Select the add-ons you want to track",
|
||||
"tracked_integrations": "Select the integrations you want to track",
|
||||
"tracked_custom_integrations": "Select the custom integrations you want to track"
|
||||
}
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/android_ip_webcam",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["pydroid-ipcam==2.0.0"]
|
||||
"requirements": ["pydroid-ipcam==3.0.0"]
|
||||
}
|
||||
|
@@ -73,7 +73,7 @@ class AndroidTVRemoteBaseEntity(Entity):
|
||||
self._api.send_key_command(key_code, direction)
|
||||
except ConnectionClosed as exc:
|
||||
raise HomeAssistantError(
|
||||
"Connection to Android TV device is closed"
|
||||
translation_domain=DOMAIN, translation_key="connection_closed"
|
||||
) from exc
|
||||
|
||||
def _send_launch_app_command(self, app_link: str) -> None:
|
||||
@@ -85,5 +85,5 @@ class AndroidTVRemoteBaseEntity(Entity):
|
||||
self._api.send_launch_app_command(app_link)
|
||||
except ConnectionClosed as exc:
|
||||
raise HomeAssistantError(
|
||||
"Connection to Android TV device is closed"
|
||||
translation_domain=DOMAIN, translation_key="connection_closed"
|
||||
) from exc
|
||||
|
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["androidtvremote2"],
|
||||
"requirements": ["androidtvremote2==0.2.0"],
|
||||
"requirements": ["androidtvremote2==0.2.1"],
|
||||
"zeroconf": ["_androidtvremote2._tcp.local."]
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AndroidTVRemoteConfigEntry
|
||||
from .const import CONF_APP_ICON, CONF_APP_NAME
|
||||
from .const import CONF_APP_ICON, CONF_APP_NAME, DOMAIN
|
||||
from .entity import AndroidTVRemoteBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -233,5 +233,5 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
|
||||
await asyncio.sleep(delay_secs)
|
||||
except ConnectionClosed as exc:
|
||||
raise HomeAssistantError(
|
||||
"Connection to Android TV device is closed"
|
||||
translation_domain=DOMAIN, translation_key="connection_closed"
|
||||
) from exc
|
||||
|
@@ -54,5 +54,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"connection_closed": {
|
||||
"message": "Connection to the Android TV device is closed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from anova_wifi import AnovaApi, InvalidLogin
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -11,8 +13,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
class AnovaConfligFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
class AnovaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Sets up a config flow for Anova."""
|
||||
|
||||
VERSION = 1
|
||||
@@ -35,7 +39,8 @@ class AnovaConfligFlow(ConfigFlow, domain=DOMAIN):
|
||||
await api.authenticate()
|
||||
except InvalidLogin:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # noqa: BLE001
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
|
@@ -22,6 +22,7 @@ from . import AnthemavConfigEntry
|
||||
from .const import ANTHEMAV_UPDATE_SIGNAL, DOMAIN, MANUFACTURER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
VOLUME_STEP = 0.01
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -60,6 +61,7 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
)
|
||||
_attr_volume_step = VOLUME_STEP
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from functools import partial
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
@@ -34,10 +35,12 @@ from .const import (
|
||||
CONF_PROMPT,
|
||||
CONF_RECOMMENDED,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_THINKING_BUDGET,
|
||||
DOMAIN,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
RECOMMENDED_THINKING_BUDGET,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -50,7 +53,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
|
||||
RECOMMENDED_OPTIONS = {
|
||||
CONF_RECOMMENDED: True,
|
||||
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||
CONF_LLM_HASS_API: [llm.LLM_API_ASSIST],
|
||||
CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
|
||||
}
|
||||
|
||||
@@ -128,25 +131,36 @@ class AnthropicOptionsFlow(OptionsFlow):
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
|
||||
if user_input[CONF_LLM_HASS_API] == "none":
|
||||
user_input.pop(CONF_LLM_HASS_API)
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
if not user_input.get(CONF_LLM_HASS_API):
|
||||
user_input.pop(CONF_LLM_HASS_API, None)
|
||||
if user_input.get(
|
||||
CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET
|
||||
) >= user_input.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS):
|
||||
errors[CONF_THINKING_BUDGET] = "thinking_budget_too_large"
|
||||
|
||||
# Re-render the options again, now with the recommended options shown/hidden
|
||||
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
||||
if not errors:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
else:
|
||||
# Re-render the options again, now with the recommended options shown/hidden
|
||||
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
|
||||
|
||||
options = {
|
||||
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
|
||||
CONF_PROMPT: user_input[CONF_PROMPT],
|
||||
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
|
||||
}
|
||||
options = {
|
||||
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
|
||||
CONF_PROMPT: user_input[CONF_PROMPT],
|
||||
CONF_LLM_HASS_API: user_input.get(CONF_LLM_HASS_API),
|
||||
}
|
||||
|
||||
suggested_values = options.copy()
|
||||
if not suggested_values.get(CONF_PROMPT):
|
||||
suggested_values[CONF_PROMPT] = llm.DEFAULT_INSTRUCTIONS_PROMPT
|
||||
if (
|
||||
suggested_llm_apis := suggested_values.get(CONF_LLM_HASS_API)
|
||||
) and isinstance(suggested_llm_apis, str):
|
||||
suggested_values[CONF_LLM_HASS_API] = [suggested_llm_apis]
|
||||
|
||||
schema = self.add_suggested_values_to_schema(
|
||||
vol.Schema(anthropic_config_option_schema(self.hass, options)),
|
||||
@@ -156,33 +170,28 @@ class AnthropicOptionsFlow(OptionsFlow):
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=schema,
|
||||
errors=errors or None,
|
||||
)
|
||||
|
||||
|
||||
def anthropic_config_option_schema(
|
||||
hass: HomeAssistant,
|
||||
options: dict[str, Any] | MappingProxyType[str, Any],
|
||||
options: Mapping[str, Any],
|
||||
) -> dict:
|
||||
"""Return a schema for Anthropic completion options."""
|
||||
hass_apis: list[SelectOptionDict] = [
|
||||
SelectOptionDict(
|
||||
label="No control",
|
||||
value="none",
|
||||
)
|
||||
]
|
||||
hass_apis.extend(
|
||||
SelectOptionDict(
|
||||
label=api.name,
|
||||
value=api.id,
|
||||
)
|
||||
for api in llm.async_get_apis(hass)
|
||||
)
|
||||
]
|
||||
|
||||
schema = {
|
||||
vol.Optional(CONF_PROMPT): TemplateSelector(),
|
||||
vol.Optional(CONF_LLM_HASS_API, default="none"): SelectSelector(
|
||||
SelectSelectorConfig(options=hass_apis)
|
||||
),
|
||||
vol.Optional(
|
||||
CONF_LLM_HASS_API,
|
||||
): SelectSelector(SelectSelectorConfig(options=hass_apis, multiple=True)),
|
||||
vol.Required(
|
||||
CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
|
||||
): bool,
|
||||
@@ -205,6 +214,10 @@ def anthropic_config_option_schema(
|
||||
CONF_TEMPERATURE,
|
||||
default=RECOMMENDED_TEMPERATURE,
|
||||
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
||||
vol.Optional(
|
||||
CONF_THINKING_BUDGET,
|
||||
default=RECOMMENDED_THINKING_BUDGET,
|
||||
): int,
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
@@ -13,3 +13,8 @@ CONF_MAX_TOKENS = "max_tokens"
|
||||
RECOMMENDED_MAX_TOKENS = 1024
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
RECOMMENDED_TEMPERATURE = 1.0
|
||||
CONF_THINKING_BUDGET = "thinking_budget"
|
||||
RECOMMENDED_THINKING_BUDGET = 0
|
||||
MIN_THINKING_BUDGET = 1024
|
||||
|
||||
THINKING_MODELS = ["claude-3-7-sonnet-20250219", "claude-3-7-sonnet-latest"]
|
||||
|
@@ -1,27 +1,39 @@
|
||||
"""Conversation support for Anthropic."""
|
||||
|
||||
from collections.abc import AsyncGenerator, Callable
|
||||
from collections.abc import AsyncGenerator, Callable, Iterable
|
||||
import json
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
import anthropic
|
||||
from anthropic import AsyncStream
|
||||
from anthropic._types import NOT_GIVEN
|
||||
from anthropic.types import (
|
||||
InputJSONDelta,
|
||||
Message,
|
||||
MessageDeltaUsage,
|
||||
MessageParam,
|
||||
MessageStreamEvent,
|
||||
RawContentBlockDeltaEvent,
|
||||
RawContentBlockStartEvent,
|
||||
RawContentBlockStopEvent,
|
||||
RawMessageDeltaEvent,
|
||||
RawMessageStartEvent,
|
||||
RawMessageStopEvent,
|
||||
RedactedThinkingBlock,
|
||||
RedactedThinkingBlockParam,
|
||||
SignatureDelta,
|
||||
TextBlock,
|
||||
TextBlockParam,
|
||||
TextDelta,
|
||||
ThinkingBlock,
|
||||
ThinkingBlockParam,
|
||||
ThinkingConfigDisabledParam,
|
||||
ThinkingConfigEnabledParam,
|
||||
ThinkingDelta,
|
||||
ToolParam,
|
||||
ToolResultBlockParam,
|
||||
ToolUseBlock,
|
||||
ToolUseBlockParam,
|
||||
Usage,
|
||||
)
|
||||
from voluptuous_openapi import convert
|
||||
|
||||
@@ -30,7 +42,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import chat_session, device_registry as dr, intent, llm
|
||||
from homeassistant.helpers import device_registry as dr, intent, llm
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AnthropicConfigEntry
|
||||
@@ -39,11 +51,15 @@ from .const import (
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_THINKING_BUDGET,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
MIN_THINKING_BUDGET,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
RECOMMENDED_THINKING_BUDGET,
|
||||
THINKING_MODELS,
|
||||
)
|
||||
|
||||
# Max number of back and forth with the LLM to generate a response
|
||||
@@ -71,73 +87,102 @@ def _format_tool(
|
||||
)
|
||||
|
||||
|
||||
def _message_convert(
|
||||
message: Message,
|
||||
) -> MessageParam:
|
||||
"""Convert from class to TypedDict."""
|
||||
param_content: list[TextBlockParam | ToolUseBlockParam] = []
|
||||
def _convert_content(
|
||||
chat_content: Iterable[conversation.Content],
|
||||
) -> list[MessageParam]:
|
||||
"""Transform HA chat_log content into Anthropic API format."""
|
||||
messages: list[MessageParam] = []
|
||||
|
||||
for message_content in message.content:
|
||||
if isinstance(message_content, TextBlock):
|
||||
param_content.append(TextBlockParam(type="text", text=message_content.text))
|
||||
elif isinstance(message_content, ToolUseBlock):
|
||||
param_content.append(
|
||||
ToolUseBlockParam(
|
||||
type="tool_use",
|
||||
id=message_content.id,
|
||||
name=message_content.name,
|
||||
input=message_content.input,
|
||||
)
|
||||
for content in chat_content:
|
||||
if isinstance(content, conversation.ToolResultContent):
|
||||
tool_result_block = ToolResultBlockParam(
|
||||
type="tool_result",
|
||||
tool_use_id=content.tool_call_id,
|
||||
content=json.dumps(content.tool_result),
|
||||
)
|
||||
|
||||
return MessageParam(role=message.role, content=param_content)
|
||||
|
||||
|
||||
def _convert_content(chat_content: conversation.Content) -> MessageParam:
|
||||
"""Create tool response content."""
|
||||
if isinstance(chat_content, conversation.ToolResultContent):
|
||||
return MessageParam(
|
||||
role="user",
|
||||
content=[
|
||||
ToolResultBlockParam(
|
||||
type="tool_result",
|
||||
tool_use_id=chat_content.tool_call_id,
|
||||
content=json.dumps(chat_content.tool_result),
|
||||
)
|
||||
],
|
||||
)
|
||||
if isinstance(chat_content, conversation.AssistantContent):
|
||||
return MessageParam(
|
||||
role="assistant",
|
||||
content=[
|
||||
TextBlockParam(type="text", text=chat_content.content or ""),
|
||||
*[
|
||||
ToolUseBlockParam(
|
||||
type="tool_use",
|
||||
id=tool_call.id,
|
||||
name=tool_call.tool_name,
|
||||
input=tool_call.tool_args,
|
||||
if not messages or messages[-1]["role"] != "user":
|
||||
messages.append(
|
||||
MessageParam(
|
||||
role="user",
|
||||
content=[tool_result_block],
|
||||
)
|
||||
for tool_call in chat_content.tool_calls or ()
|
||||
],
|
||||
],
|
||||
)
|
||||
if isinstance(chat_content, conversation.UserContent):
|
||||
return MessageParam(
|
||||
role="user",
|
||||
content=chat_content.content,
|
||||
)
|
||||
# Note: We don't pass SystemContent here as its passed to the API as the prompt
|
||||
raise ValueError(f"Unexpected content type: {type(chat_content)}")
|
||||
)
|
||||
elif isinstance(messages[-1]["content"], str):
|
||||
messages[-1]["content"] = [
|
||||
TextBlockParam(type="text", text=messages[-1]["content"]),
|
||||
tool_result_block,
|
||||
]
|
||||
else:
|
||||
messages[-1]["content"].append(tool_result_block) # type: ignore[attr-defined]
|
||||
elif isinstance(content, conversation.UserContent):
|
||||
# Combine consequent user messages
|
||||
if not messages or messages[-1]["role"] != "user":
|
||||
messages.append(
|
||||
MessageParam(
|
||||
role="user",
|
||||
content=content.content,
|
||||
)
|
||||
)
|
||||
elif isinstance(messages[-1]["content"], str):
|
||||
messages[-1]["content"] = [
|
||||
TextBlockParam(type="text", text=messages[-1]["content"]),
|
||||
TextBlockParam(type="text", text=content.content),
|
||||
]
|
||||
else:
|
||||
messages[-1]["content"].append( # type: ignore[attr-defined]
|
||||
TextBlockParam(type="text", text=content.content)
|
||||
)
|
||||
elif isinstance(content, conversation.AssistantContent):
|
||||
# Combine consequent assistant messages
|
||||
if not messages or messages[-1]["role"] != "assistant":
|
||||
messages.append(
|
||||
MessageParam(
|
||||
role="assistant",
|
||||
content=[],
|
||||
)
|
||||
)
|
||||
|
||||
if content.content:
|
||||
messages[-1]["content"].append( # type: ignore[union-attr]
|
||||
TextBlockParam(type="text", text=content.content)
|
||||
)
|
||||
if content.tool_calls:
|
||||
messages[-1]["content"].extend( # type: ignore[union-attr]
|
||||
[
|
||||
ToolUseBlockParam(
|
||||
type="tool_use",
|
||||
id=tool_call.id,
|
||||
name=tool_call.tool_name,
|
||||
input=tool_call.tool_args,
|
||||
)
|
||||
for tool_call in content.tool_calls
|
||||
]
|
||||
)
|
||||
else:
|
||||
# Note: We don't pass SystemContent here as its passed to the API as the prompt
|
||||
raise TypeError(f"Unexpected content type: {type(content)}")
|
||||
|
||||
return messages
|
||||
|
||||
|
||||
async def _transform_stream(
|
||||
async def _transform_stream( # noqa: C901 - This is complex, but better to have it in one place
|
||||
chat_log: conversation.ChatLog,
|
||||
result: AsyncStream[MessageStreamEvent],
|
||||
messages: list[MessageParam],
|
||||
) -> AsyncGenerator[conversation.AssistantContentDeltaDict]:
|
||||
"""Transform the response stream into HA format.
|
||||
|
||||
A typical stream of responses might look something like the following:
|
||||
- RawMessageStartEvent with no content
|
||||
- RawContentBlockStartEvent with an empty ThinkingBlock (if extended thinking is enabled)
|
||||
- RawContentBlockDeltaEvent with a ThinkingDelta
|
||||
- RawContentBlockDeltaEvent with a ThinkingDelta
|
||||
- RawContentBlockDeltaEvent with a ThinkingDelta
|
||||
- ...
|
||||
- RawContentBlockDeltaEvent with a SignatureDelta
|
||||
- RawContentBlockStopEvent
|
||||
- RawContentBlockStartEvent with a RedactedThinkingBlock (occasionally)
|
||||
- RawContentBlockStopEvent (RedactedThinkingBlock does not have a delta)
|
||||
- RawContentBlockStartEvent with an empty TextBlock
|
||||
- RawContentBlockDeltaEvent with a TextDelta
|
||||
- RawContentBlockDeltaEvent with a TextDelta
|
||||
@@ -151,44 +196,127 @@ async def _transform_stream(
|
||||
- RawContentBlockStopEvent
|
||||
- RawMessageDeltaEvent with a stop_reason='tool_use'
|
||||
- RawMessageStopEvent(type='message_stop')
|
||||
|
||||
Each message could contain multiple blocks of the same type.
|
||||
"""
|
||||
if result is None:
|
||||
raise TypeError("Expected a stream of messages")
|
||||
|
||||
current_tool_call: dict | None = None
|
||||
current_message: MessageParam | None = None
|
||||
current_block: (
|
||||
TextBlockParam
|
||||
| ToolUseBlockParam
|
||||
| ThinkingBlockParam
|
||||
| RedactedThinkingBlockParam
|
||||
| None
|
||||
) = None
|
||||
current_tool_args: str
|
||||
input_usage: Usage | None = None
|
||||
|
||||
async for response in result:
|
||||
LOGGER.debug("Received response: %s", response)
|
||||
|
||||
if isinstance(response, RawContentBlockStartEvent):
|
||||
if isinstance(response, RawMessageStartEvent):
|
||||
if response.message.role != "assistant":
|
||||
raise ValueError("Unexpected message role")
|
||||
current_message = MessageParam(role=response.message.role, content=[])
|
||||
input_usage = response.message.usage
|
||||
elif isinstance(response, RawContentBlockStartEvent):
|
||||
if isinstance(response.content_block, ToolUseBlock):
|
||||
current_tool_call = {
|
||||
"id": response.content_block.id,
|
||||
"name": response.content_block.name,
|
||||
"input": "",
|
||||
}
|
||||
current_block = ToolUseBlockParam(
|
||||
type="tool_use",
|
||||
id=response.content_block.id,
|
||||
name=response.content_block.name,
|
||||
input="",
|
||||
)
|
||||
current_tool_args = ""
|
||||
elif isinstance(response.content_block, TextBlock):
|
||||
current_block = TextBlockParam(
|
||||
type="text", text=response.content_block.text
|
||||
)
|
||||
yield {"role": "assistant"}
|
||||
if response.content_block.text:
|
||||
yield {"content": response.content_block.text}
|
||||
elif isinstance(response.content_block, ThinkingBlock):
|
||||
current_block = ThinkingBlockParam(
|
||||
type="thinking",
|
||||
thinking=response.content_block.thinking,
|
||||
signature=response.content_block.signature,
|
||||
)
|
||||
elif isinstance(response.content_block, RedactedThinkingBlock):
|
||||
current_block = RedactedThinkingBlockParam(
|
||||
type="redacted_thinking", data=response.content_block.data
|
||||
)
|
||||
LOGGER.debug(
|
||||
"Some of Claude’s internal reasoning has been automatically "
|
||||
"encrypted for safety reasons. This doesn’t affect the quality of "
|
||||
"responses"
|
||||
)
|
||||
elif isinstance(response, RawContentBlockDeltaEvent):
|
||||
if current_block is None:
|
||||
raise ValueError("Unexpected delta without a block")
|
||||
if isinstance(response.delta, InputJSONDelta):
|
||||
if current_tool_call is None:
|
||||
raise ValueError("Unexpected delta without a tool call")
|
||||
current_tool_call["input"] += response.delta.partial_json
|
||||
current_tool_args += response.delta.partial_json
|
||||
elif isinstance(response.delta, TextDelta):
|
||||
LOGGER.debug("yielding delta: %s", response.delta.text)
|
||||
text_block = cast(TextBlockParam, current_block)
|
||||
text_block["text"] += response.delta.text
|
||||
yield {"content": response.delta.text}
|
||||
elif isinstance(response.delta, ThinkingDelta):
|
||||
thinking_block = cast(ThinkingBlockParam, current_block)
|
||||
thinking_block["thinking"] += response.delta.thinking
|
||||
elif isinstance(response.delta, SignatureDelta):
|
||||
thinking_block = cast(ThinkingBlockParam, current_block)
|
||||
thinking_block["signature"] += response.delta.signature
|
||||
elif isinstance(response, RawContentBlockStopEvent):
|
||||
if current_tool_call:
|
||||
if current_block is None:
|
||||
raise ValueError("Unexpected stop event without a current block")
|
||||
if current_block["type"] == "tool_use":
|
||||
# tool block
|
||||
tool_args = json.loads(current_tool_args) if current_tool_args else {}
|
||||
current_block["input"] = tool_args
|
||||
yield {
|
||||
"tool_calls": [
|
||||
llm.ToolInput(
|
||||
id=current_tool_call["id"],
|
||||
tool_name=current_tool_call["name"],
|
||||
tool_args=json.loads(current_tool_call["input"]),
|
||||
id=current_block["id"],
|
||||
tool_name=current_block["name"],
|
||||
tool_args=tool_args,
|
||||
)
|
||||
]
|
||||
}
|
||||
current_tool_call = None
|
||||
elif current_block["type"] == "thinking":
|
||||
# thinking block
|
||||
LOGGER.debug("Thinking: %s", current_block["thinking"])
|
||||
|
||||
if current_message is None:
|
||||
raise ValueError("Unexpected stop event without a current message")
|
||||
current_message["content"].append(current_block) # type: ignore[union-attr]
|
||||
current_block = None
|
||||
elif isinstance(response, RawMessageDeltaEvent):
|
||||
if (usage := response.usage) is not None:
|
||||
chat_log.async_trace(_create_token_stats(input_usage, usage))
|
||||
elif isinstance(response, RawMessageStopEvent):
|
||||
if current_message is not None:
|
||||
messages.append(current_message)
|
||||
current_message = None
|
||||
|
||||
|
||||
def _create_token_stats(
|
||||
input_usage: Usage | None, response_usage: MessageDeltaUsage
|
||||
) -> dict[str, Any]:
|
||||
"""Create token stats for conversation agent tracing."""
|
||||
input_tokens = 0
|
||||
cached_input_tokens = 0
|
||||
if input_usage:
|
||||
input_tokens = input_usage.input_tokens
|
||||
cached_input_tokens = input_usage.cache_creation_input_tokens or 0
|
||||
output_tokens = response_usage.output_tokens
|
||||
return {
|
||||
"stats": {
|
||||
"input_tokens": input_tokens,
|
||||
"cached_input_tokens": cached_input_tokens,
|
||||
"output_tokens": output_tokens,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AnthropicConversationEntity(
|
||||
@@ -226,18 +354,6 @@ class AnthropicConversationEntity(
|
||||
self.entry.add_update_listener(self._async_entry_update_listener)
|
||||
)
|
||||
|
||||
async def async_process(
|
||||
self, user_input: conversation.ConversationInput
|
||||
) -> conversation.ConversationResult:
|
||||
"""Process a sentence."""
|
||||
with (
|
||||
chat_session.async_get_chat_session(
|
||||
self.hass, user_input.conversation_id
|
||||
) as session,
|
||||
conversation.async_get_chat_log(self.hass, session, user_input) as chat_log,
|
||||
):
|
||||
return await self._async_handle_message(user_input, chat_log)
|
||||
|
||||
async def _async_handle_message(
|
||||
self,
|
||||
user_input: conversation.ConversationInput,
|
||||
@@ -266,34 +382,51 @@ class AnthropicConversationEntity(
|
||||
system = chat_log.content[0]
|
||||
if not isinstance(system, conversation.SystemContent):
|
||||
raise TypeError("First message must be a system message")
|
||||
messages = [_convert_content(content) for content in chat_log.content[1:]]
|
||||
messages = _convert_content(chat_log.content[1:])
|
||||
|
||||
client = self.entry.runtime_data
|
||||
|
||||
thinking_budget = options.get(CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET)
|
||||
model = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
|
||||
|
||||
# To prevent infinite loops, we limit the number of iterations
|
||||
for _iteration in range(MAX_TOOL_ITERATIONS):
|
||||
try:
|
||||
stream = await client.messages.create(
|
||||
model=options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
|
||||
messages=messages,
|
||||
tools=tools or NOT_GIVEN,
|
||||
max_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
|
||||
system=system.content,
|
||||
temperature=options.get(CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE),
|
||||
stream=True,
|
||||
model_args = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"tools": tools or NOT_GIVEN,
|
||||
"max_tokens": options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
|
||||
"system": system.content,
|
||||
"stream": True,
|
||||
}
|
||||
if model in THINKING_MODELS and thinking_budget >= MIN_THINKING_BUDGET:
|
||||
model_args["thinking"] = ThinkingConfigEnabledParam(
|
||||
type="enabled", budget_tokens=thinking_budget
|
||||
)
|
||||
else:
|
||||
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
|
||||
model_args["temperature"] = options.get(
|
||||
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
|
||||
)
|
||||
|
||||
try:
|
||||
stream = await client.messages.create(**model_args)
|
||||
except anthropic.AnthropicError as err:
|
||||
raise HomeAssistantError(
|
||||
f"Sorry, I had a problem talking to Anthropic: {err}"
|
||||
) from err
|
||||
|
||||
messages.extend(
|
||||
[
|
||||
_convert_content(content)
|
||||
async for content in chat_log.async_add_delta_content_stream(
|
||||
user_input.agent_id, _transform_stream(stream)
|
||||
)
|
||||
]
|
||||
_convert_content(
|
||||
[
|
||||
content
|
||||
async for content in chat_log.async_add_delta_content_stream(
|
||||
user_input.agent_id,
|
||||
_transform_stream(chat_log, stream, messages),
|
||||
)
|
||||
if not isinstance(content, conversation.AssistantContent)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if not chat_log.unresponded_tool_results:
|
||||
@@ -305,7 +438,9 @@ class AnthropicConversationEntity(
|
||||
intent_response = intent.IntentResponse(language=user_input.language)
|
||||
intent_response.async_set_speech(response_content.content or "")
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=chat_log.conversation_id
|
||||
response=intent_response,
|
||||
conversation_id=chat_log.conversation_id,
|
||||
continue_conversation=chat_log.continue_conversation,
|
||||
)
|
||||
|
||||
async def _async_entry_update_listener(
|
||||
|
@@ -23,12 +23,17 @@
|
||||
"max_tokens": "Maximum tokens to return in response",
|
||||
"temperature": "Temperature",
|
||||
"llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]",
|
||||
"recommended": "Recommended model settings"
|
||||
"recommended": "Recommended model settings",
|
||||
"thinking_budget_tokens": "Thinking budget"
|
||||
},
|
||||
"data_description": {
|
||||
"prompt": "Instruct how the LLM should respond. This can be a template."
|
||||
"prompt": "Instruct how the LLM should respond. This can be a template.",
|
||||
"thinking_budget_tokens": "The number of tokens the model can use to think about the response out of the total maximum number of tokens. Set to 1024 or greater to enable extended thinking."
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"thinking_budget_too_large": "Maximum tokens must be greater than the thinking budget."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -53,10 +53,8 @@ class OnlineStatus(CoordinatorEntity[APCUPSdCoordinator], BinarySensorEntity):
|
||||
"""Initialize the APCUPSd binary device."""
|
||||
super().__init__(coordinator, context=description.key.upper())
|
||||
|
||||
# Set up unique id and device info if serial number is available.
|
||||
if (serial_no := coordinator.data.serial_no) is not None:
|
||||
self._attr_unique_id = f"{serial_no}_{description.key}"
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.unique_device_id}_{description.key}"
|
||||
self._attr_device_info = coordinator.device_info
|
||||
|
||||
@property
|
||||
|
@@ -85,11 +85,16 @@ class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]):
|
||||
self._host = host
|
||||
self._port = port
|
||||
|
||||
@property
|
||||
def unique_device_id(self) -> str:
|
||||
"""Return a unique ID of the device, which is the serial number (if available) or the config entry ID."""
|
||||
return self.data.serial_no or self.config_entry.entry_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the DeviceInfo of this APC UPS, if serial number is available."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self.data.serial_no or self.config_entry.entry_id)},
|
||||
identifiers={(DOMAIN, self.unique_device_id)},
|
||||
model=self.data.model,
|
||||
manufacturer="APC",
|
||||
name=self.data.name or "APC UPS",
|
||||
@@ -108,4 +113,7 @@ class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]):
|
||||
data = await aioapcaccess.request_status(self._host, self._port)
|
||||
return APCUPSdData(data)
|
||||
except (OSError, asyncio.IncompleteReadError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
) from error
|
||||
|
@@ -458,11 +458,8 @@ class APCUPSdSensor(CoordinatorEntity[APCUPSdCoordinator], SensorEntity):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator=coordinator, context=description.key.upper())
|
||||
|
||||
# Set up unique id and device info if serial number is available.
|
||||
if (serial_no := coordinator.data.serial_no) is not None:
|
||||
self._attr_unique_id = f"{serial_no}_{description.key}"
|
||||
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.unique_device_id}_{description.key}"
|
||||
self._attr_device_info = coordinator.device_info
|
||||
|
||||
# Initial update of attributes.
|
||||
|
@@ -57,7 +57,7 @@
|
||||
"name": "Status date"
|
||||
},
|
||||
"dip_switch_settings": {
|
||||
"name": "Dip switch settings"
|
||||
"name": "DIP switch settings"
|
||||
},
|
||||
"low_battery_signal": {
|
||||
"name": "Low battery signal"
|
||||
@@ -93,7 +93,7 @@
|
||||
"name": "Internal temperature"
|
||||
},
|
||||
"last_self_test": {
|
||||
"name": "Last self test"
|
||||
"name": "Last self-test"
|
||||
},
|
||||
"last_transfer": {
|
||||
"name": "Last transfer"
|
||||
@@ -177,7 +177,7 @@
|
||||
"name": "Restore requirement"
|
||||
},
|
||||
"self_test_result": {
|
||||
"name": "Self test result"
|
||||
"name": "Self-test result"
|
||||
},
|
||||
"sensitivity": {
|
||||
"name": "Sensitivity"
|
||||
@@ -195,7 +195,7 @@
|
||||
"name": "Status"
|
||||
},
|
||||
"self_test_interval": {
|
||||
"name": "Self test interval"
|
||||
"name": "Self-test interval"
|
||||
},
|
||||
"time_left": {
|
||||
"name": "Time left"
|
||||
@@ -219,5 +219,10 @@
|
||||
"name": "Transfer to battery"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"cannot_connect": {
|
||||
"message": "Cannot connect to APC UPS Daemon."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
homeassistant/components/apollo_automation/__init__.py
Normal file
1
homeassistant/components/apollo_automation/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Virtual integration: Apollo Automation."""
|
6
homeassistant/components/apollo_automation/manifest.json
Normal file
6
homeassistant/components/apollo_automation/manifest.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"domain": "apollo_automation",
|
||||
"name": "Apollo Automation",
|
||||
"integration_type": "virtual",
|
||||
"supported_by": "esphome"
|
||||
}
|
@@ -20,6 +20,7 @@ import voluptuous as vol
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_REAUTH,
|
||||
SOURCE_ZEROCONF,
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
@@ -381,7 +382,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_IDENTIFIERS: list(combined_identifiers),
|
||||
},
|
||||
)
|
||||
if entry.source != SOURCE_IGNORE:
|
||||
# Don't reload ignored entries or in the middle of reauth,
|
||||
# e.g. if the user is entering a new PIN
|
||||
if entry.source != SOURCE_IGNORE and self.source != SOURCE_REAUTH:
|
||||
self.hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
if not allow_exist:
|
||||
raise DeviceAlreadyConfigured
|
||||
|
@@ -120,6 +120,7 @@ class AppleTvMediaPlayer(
|
||||
"""Initialize the Apple TV media player."""
|
||||
super().__init__(name, identifier, manager)
|
||||
self._playing: Playing | None = None
|
||||
self._playing_last_updated: datetime | None = None
|
||||
self._app_list: dict[str, str] = {}
|
||||
|
||||
@callback
|
||||
@@ -209,6 +210,7 @@ class AppleTvMediaPlayer(
|
||||
This is a callback function from pyatv.interface.PushListener.
|
||||
"""
|
||||
self._playing = playstatus
|
||||
self._playing_last_updated = dt_util.utcnow()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
@@ -316,7 +318,7 @@ class AppleTvMediaPlayer(
|
||||
def media_position_updated_at(self) -> datetime | None:
|
||||
"""Last valid time of media position."""
|
||||
if self.state in {MediaPlayerState.PLAYING, MediaPlayerState.PAUSED}:
|
||||
return dt_util.utcnow()
|
||||
return self._playing_last_updated
|
||||
return None
|
||||
|
||||
async def async_play_media(
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyaprilaire"],
|
||||
"requirements": ["pyaprilaire==0.7.7"]
|
||||
"requirements": ["pyaprilaire==0.8.1"]
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
|
||||
|
||||
config_entry: ApSystemsConfigEntry
|
||||
device_version: str
|
||||
battery_system: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -68,6 +69,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
|
||||
self.api.max_power = device_info.maxPower
|
||||
self.api.min_power = device_info.minPower
|
||||
self.device_version = device_info.devVer
|
||||
self.battery_system = device_info.isBatterySystem
|
||||
|
||||
async def _async_update_data(self) -> ApSystemsSensorData:
|
||||
try:
|
||||
|
@@ -6,5 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apsystems",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["apsystems-ez1==2.4.0"]
|
||||
"loggers": ["APsystemsEZ1"],
|
||||
"requirements": ["apsystems-ez1==2.6.0"]
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"off_grid_status": {
|
||||
"name": "Off grid status"
|
||||
"name": "Off-grid status"
|
||||
},
|
||||
"dc_1_short_circuit_error_status": {
|
||||
"name": "DC 1 short circuit error status"
|
||||
|
@@ -36,6 +36,8 @@ class ApSystemsInverterSwitch(ApSystemsEntity, SwitchEntity):
|
||||
super().__init__(data)
|
||||
self._api = data.coordinator.api
|
||||
self._attr_unique_id = f"{data.device_id}_inverter_status"
|
||||
if data.coordinator.battery_system:
|
||||
self._attr_available = False
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update switch status and availability."""
|
||||
|
@@ -60,7 +60,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "cannot_connect"
|
||||
except AuthenticationFailed:
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
|
@@ -36,9 +36,9 @@
|
||||
"wi_fi_strength": {
|
||||
"name": "Wi-Fi strength",
|
||||
"state": {
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High"
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"high": "[%key:common::state::high%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@
|
||||
"sensor": {
|
||||
"threshold": {
|
||||
"state": {
|
||||
"error": "Error",
|
||||
"error": "[%key:common::state::error%]",
|
||||
"green": "Green",
|
||||
"yellow": "Yellow",
|
||||
"red": "Red"
|
||||
|
@@ -6,7 +6,11 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import DEGREE, UnitOfPrecipitationDepth, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@@ -98,6 +102,7 @@ def discover_sensors(topic: str, payload: dict[str, Any]) -> list[ArwnSensor] |
|
||||
DEGREE,
|
||||
"mdi:compass",
|
||||
device_class=SensorDeviceClass.WIND_DIRECTION,
|
||||
state_class=SensorStateClass.MEASUREMENT_ANGLE,
|
||||
),
|
||||
]
|
||||
return None
|
||||
@@ -178,6 +183,7 @@ class ArwnSensor(SensorEntity):
|
||||
units: str,
|
||||
icon: str | None = None,
|
||||
device_class: SensorDeviceClass | None = None,
|
||||
state_class: SensorStateClass | None = None,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.entity_id = _slug(name)
|
||||
@@ -188,6 +194,7 @@ class ArwnSensor(SensorEntity):
|
||||
self._attr_native_unit_of_measurement = units
|
||||
self._attr_icon = icon
|
||||
self._attr_device_class = device_class
|
||||
self._attr_state_class = state_class
|
||||
|
||||
def set_event(self, event: dict[str, Any]) -> None:
|
||||
"""Update the sensor with the most recent event."""
|
||||
|
@@ -125,7 +125,7 @@ SAVE_DELAY = 10
|
||||
@callback
|
||||
def _async_local_fallback_intent_filter(result: RecognizeResult) -> bool:
|
||||
"""Filter out intents that are not local fallback."""
|
||||
return result.intent.name in (intent.INTENT_GET_STATE, intent.INTENT_NEVERMIND)
|
||||
return result.intent.name in (intent.INTENT_GET_STATE)
|
||||
|
||||
|
||||
@callback
|
||||
@@ -649,6 +649,7 @@ class PipelineRun:
|
||||
data["runner_data"] = self.runner_data
|
||||
if self.tts_stream:
|
||||
data["tts_output"] = {
|
||||
"token": self.tts_stream.token,
|
||||
"url": self.tts_stream.url,
|
||||
"mime_type": self.tts_stream.content_type,
|
||||
}
|
||||
@@ -1295,6 +1296,7 @@ class PipelineRun:
|
||||
|
||||
tts_output = {
|
||||
"media_id": tts_media_id,
|
||||
"token": self.tts_stream.token,
|
||||
"url": self.tts_stream.url,
|
||||
"mime_type": self.tts_stream.content_type,
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
"""Base class for assist satellite entities."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import StaticPathConfig
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -15,6 +17,8 @@ from .const import (
|
||||
CONNECTION_TEST_DATA,
|
||||
DATA_COMPONENT,
|
||||
DOMAIN,
|
||||
PREANNOUNCE_FILENAME,
|
||||
PREANNOUNCE_URL,
|
||||
AssistSatelliteEntityFeature,
|
||||
)
|
||||
from .entity import (
|
||||
@@ -56,6 +60,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
{
|
||||
vol.Optional("message"): str,
|
||||
vol.Optional("media_id"): str,
|
||||
vol.Optional("preannounce"): bool,
|
||||
vol.Optional("preannounce_media_id"): str,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key("message", "media_id"),
|
||||
@@ -70,6 +76,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
{
|
||||
vol.Optional("start_message"): str,
|
||||
vol.Optional("start_media_id"): str,
|
||||
vol.Optional("preannounce"): bool,
|
||||
vol.Optional("preannounce_media_id"): str,
|
||||
vol.Optional("extra_system_prompt"): str,
|
||||
}
|
||||
),
|
||||
@@ -82,6 +90,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async_register_websocket_api(hass)
|
||||
hass.http.register_view(ConnectionTestView())
|
||||
|
||||
# Default preannounce sound
|
||||
await hass.http.async_register_static_paths(
|
||||
[
|
||||
StaticPathConfig(
|
||||
PREANNOUNCE_URL, str(Path(__file__).parent / PREANNOUNCE_FILENAME)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@@ -20,6 +20,9 @@ CONNECTION_TEST_DATA: HassKey[dict[str, asyncio.Event]] = HassKey(
|
||||
f"{DOMAIN}_connection_tests"
|
||||
)
|
||||
|
||||
PREANNOUNCE_FILENAME = "preannounce.mp3"
|
||||
PREANNOUNCE_URL = f"/api/assist_satellite/static/{PREANNOUNCE_FILENAME}"
|
||||
|
||||
|
||||
class AssistSatelliteEntityFeature(IntFlag):
|
||||
"""Supported features of Assist satellite entity."""
|
||||
|
@@ -23,15 +23,12 @@ from homeassistant.components.assist_pipeline import (
|
||||
vad,
|
||||
)
|
||||
from homeassistant.components.media_player import async_process_play_media_url
|
||||
from homeassistant.components.tts import (
|
||||
generate_media_source_id as tts_generate_media_source_id,
|
||||
)
|
||||
from homeassistant.core import Context, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import chat_session, entity
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
|
||||
from .const import AssistSatelliteEntityFeature
|
||||
from .const import PREANNOUNCE_URL, AssistSatelliteEntityFeature
|
||||
from .errors import AssistSatelliteError, SatelliteBusyError
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -98,9 +95,15 @@ class AssistSatelliteAnnouncement:
|
||||
original_media_id: str
|
||||
"""The raw media ID before processing."""
|
||||
|
||||
tts_token: str | None
|
||||
"""The TTS token of the media."""
|
||||
|
||||
media_id_source: Literal["url", "media_id", "tts"]
|
||||
"""Source of the media ID."""
|
||||
|
||||
preannounce_media_id: str | None = None
|
||||
"""Media ID to be played before announcement."""
|
||||
|
||||
|
||||
class AssistSatelliteEntity(entity.Entity):
|
||||
"""Entity encapsulating the state and functionality of an Assist satellite."""
|
||||
@@ -177,6 +180,8 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
self,
|
||||
message: str | None = None,
|
||||
media_id: str | None = None,
|
||||
preannounce: bool = True,
|
||||
preannounce_media_id: str = PREANNOUNCE_URL,
|
||||
) -> None:
|
||||
"""Play and show an announcement on the satellite.
|
||||
|
||||
@@ -186,6 +191,9 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
If media_id is provided, it is played directly. It is possible
|
||||
to omit the message and the satellite will not show any text.
|
||||
|
||||
If preannounce is True, a sound is played before the announcement.
|
||||
If preannounce_media_id is provided, it overrides the default sound.
|
||||
|
||||
Calls async_announce with message and media id.
|
||||
"""
|
||||
await self._cancel_running_pipeline()
|
||||
@@ -193,7 +201,11 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
if message is None:
|
||||
message = ""
|
||||
|
||||
announcement = await self._resolve_announcement_media_id(message, media_id)
|
||||
announcement = await self._resolve_announcement_media_id(
|
||||
message,
|
||||
media_id,
|
||||
preannounce_media_id=preannounce_media_id if preannounce else None,
|
||||
)
|
||||
|
||||
if self._is_announcing:
|
||||
raise SatelliteBusyError
|
||||
@@ -220,6 +232,8 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
start_message: str | None = None,
|
||||
start_media_id: str | None = None,
|
||||
extra_system_prompt: str | None = None,
|
||||
preannounce: bool = True,
|
||||
preannounce_media_id: str = PREANNOUNCE_URL,
|
||||
) -> None:
|
||||
"""Start a conversation from the satellite.
|
||||
|
||||
@@ -229,6 +243,9 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
If start_media_id is provided, it is played directly. It is possible
|
||||
to omit the message and the satellite will not show any text.
|
||||
|
||||
If preannounce is True, a sound is played before the start message or media.
|
||||
If preannounce_media_id is provided, it overrides the default sound.
|
||||
|
||||
Calls async_start_conversation.
|
||||
"""
|
||||
await self._cancel_running_pipeline()
|
||||
@@ -244,13 +261,17 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
start_message = ""
|
||||
|
||||
announcement = await self._resolve_announcement_media_id(
|
||||
start_message, start_media_id
|
||||
start_message,
|
||||
start_media_id,
|
||||
preannounce_media_id=preannounce_media_id if preannounce else None,
|
||||
)
|
||||
|
||||
if self._is_announcing:
|
||||
raise SatelliteBusyError
|
||||
|
||||
self._is_announcing = True
|
||||
self._set_state(AssistSatelliteState.RESPONDING)
|
||||
|
||||
# Provide our start info to the LLM so it understands context of incoming message
|
||||
if extra_system_prompt is not None:
|
||||
self._extra_system_prompt = extra_system_prompt
|
||||
@@ -280,6 +301,7 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
raise
|
||||
finally:
|
||||
self._is_announcing = False
|
||||
self._set_state(AssistSatelliteState.IDLE)
|
||||
|
||||
async def async_start_conversation(
|
||||
self, start_announcement: AssistSatelliteAnnouncement
|
||||
@@ -470,20 +492,27 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
return vad.VadSensitivity.to_seconds(vad_sensitivity)
|
||||
|
||||
async def _resolve_announcement_media_id(
|
||||
self, message: str, media_id: str | None
|
||||
self,
|
||||
message: str,
|
||||
media_id: str | None,
|
||||
preannounce_media_id: str | None = None,
|
||||
) -> AssistSatelliteAnnouncement:
|
||||
"""Resolve the media ID."""
|
||||
media_id_source: Literal["url", "media_id", "tts"] | None = None
|
||||
tts_token: str | None = None
|
||||
|
||||
if media_id:
|
||||
original_media_id = media_id
|
||||
|
||||
else:
|
||||
media_id_source = "tts"
|
||||
# Synthesize audio and get URL
|
||||
pipeline_id = self._resolve_pipeline()
|
||||
pipeline = async_get_pipeline(self.hass, pipeline_id)
|
||||
|
||||
engine = tts.async_resolve_engine(self.hass, pipeline.tts_engine)
|
||||
if engine is None:
|
||||
raise HomeAssistantError(f"TTS engine {pipeline.tts_engine} not found")
|
||||
|
||||
tts_options: dict[str, Any] = {}
|
||||
if pipeline.tts_voice is not None:
|
||||
tts_options[tts.ATTR_VOICE] = pipeline.tts_voice
|
||||
@@ -491,14 +520,23 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
if self.tts_options is not None:
|
||||
tts_options.update(self.tts_options)
|
||||
|
||||
media_id = tts_generate_media_source_id(
|
||||
stream = tts.async_create_stream(
|
||||
self.hass,
|
||||
message,
|
||||
engine=pipeline.tts_engine,
|
||||
engine=engine,
|
||||
language=pipeline.tts_language,
|
||||
options=tts_options,
|
||||
)
|
||||
stream.async_set_message(message)
|
||||
|
||||
tts_token = stream.token
|
||||
media_id = stream.url
|
||||
original_media_id = tts.generate_media_source_id(
|
||||
self.hass,
|
||||
message,
|
||||
engine=engine,
|
||||
language=pipeline.tts_language,
|
||||
options=tts_options,
|
||||
)
|
||||
original_media_id = media_id
|
||||
|
||||
if media_source.is_media_source_id(media_id):
|
||||
if not media_id_source:
|
||||
@@ -516,6 +554,26 @@ class AssistSatelliteEntity(entity.Entity):
|
||||
# Resolve to full URL
|
||||
media_id = async_process_play_media_url(self.hass, media_id)
|
||||
|
||||
# Resolve preannounce media id
|
||||
if preannounce_media_id:
|
||||
if media_source.is_media_source_id(preannounce_media_id):
|
||||
preannounce_media = await media_source.async_resolve_media(
|
||||
self.hass,
|
||||
preannounce_media_id,
|
||||
None,
|
||||
)
|
||||
preannounce_media_id = preannounce_media.url
|
||||
|
||||
# Resolve to full URL
|
||||
preannounce_media_id = async_process_play_media_url(
|
||||
self.hass, preannounce_media_id
|
||||
)
|
||||
|
||||
return AssistSatelliteAnnouncement(
|
||||
message, media_id, original_media_id, media_id_source
|
||||
message=message,
|
||||
media_id=media_id,
|
||||
original_media_id=original_media_id,
|
||||
tts_token=tts_token,
|
||||
media_id_source=media_id_source,
|
||||
preannounce_media_id=preannounce_media_id,
|
||||
)
|
||||
|
BIN
homeassistant/components/assist_satellite/preannounce.mp3
Normal file
BIN
homeassistant/components/assist_satellite/preannounce.mp3
Normal file
Binary file not shown.
@@ -8,12 +8,22 @@ announce:
|
||||
message:
|
||||
required: false
|
||||
example: "Time to wake up!"
|
||||
default: ""
|
||||
selector:
|
||||
text:
|
||||
media_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
preannounce:
|
||||
required: false
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
preannounce_media_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
start_conversation:
|
||||
target:
|
||||
entity:
|
||||
@@ -24,6 +34,7 @@ start_conversation:
|
||||
start_message:
|
||||
required: false
|
||||
example: "You left the lights on in the living room. Turn them off?"
|
||||
default: ""
|
||||
selector:
|
||||
text:
|
||||
start_media_id:
|
||||
@@ -34,3 +45,12 @@ start_conversation:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
preannounce:
|
||||
required: false
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
preannounce_media_id:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
|
@@ -23,6 +23,14 @@
|
||||
"media_id": {
|
||||
"name": "Media ID",
|
||||
"description": "The media ID to announce instead of using text-to-speech."
|
||||
},
|
||||
"preannounce": {
|
||||
"name": "Preannounce",
|
||||
"description": "Play a sound before the announcement."
|
||||
},
|
||||
"preannounce_media_id": {
|
||||
"name": "Preannounce media ID",
|
||||
"description": "Custom media ID to play before the announcement."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -41,6 +49,14 @@
|
||||
"extra_system_prompt": {
|
||||
"name": "Extra system prompt",
|
||||
"description": "Provide background information to the AI about the request."
|
||||
},
|
||||
"preannounce": {
|
||||
"name": "Preannounce",
|
||||
"description": "Play a sound before the start message or media."
|
||||
},
|
||||
"preannounce_media_id": {
|
||||
"name": "Preannounce media ID",
|
||||
"description": "Custom media ID to play before the start message or media."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -198,7 +198,8 @@ async def websocket_test_connection(
|
||||
|
||||
hass.async_create_background_task(
|
||||
satellite.async_internal_announce(
|
||||
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}"
|
||||
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}",
|
||||
preannounce=False,
|
||||
),
|
||||
f"assist_satellite_connection_test_{msg['entity_id']}",
|
||||
)
|
||||
|
@@ -2,10 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
from pyasuswrt import AsusWrtError
|
||||
@@ -363,7 +362,7 @@ class AsusWrtRouter:
|
||||
"""Add a function to call when router is closed."""
|
||||
self._on_close.append(func)
|
||||
|
||||
def update_options(self, new_options: MappingProxyType[str, Any]) -> bool:
|
||||
def update_options(self, new_options: Mapping[str, Any]) -> bool:
|
||||
"""Update router options."""
|
||||
req_reload = False
|
||||
for name, new_opt in new_options.items():
|
||||
|
@@ -66,28 +66,28 @@
|
||||
"name": "Upload"
|
||||
},
|
||||
"load_avg_1m": {
|
||||
"name": "Average load (1m)"
|
||||
"name": "Average load (1 min)"
|
||||
},
|
||||
"load_avg_5m": {
|
||||
"name": "Average load (5m)"
|
||||
"name": "Average load (5 min)"
|
||||
},
|
||||
"load_avg_15m": {
|
||||
"name": "Average load (15m)"
|
||||
"name": "Average load (15 min)"
|
||||
},
|
||||
"24ghz_temperature": {
|
||||
"name": "2.4GHz Temperature"
|
||||
"name": "2.4GHz temperature"
|
||||
},
|
||||
"5ghz_temperature": {
|
||||
"name": "5GHz Temperature"
|
||||
"name": "5GHz temperature"
|
||||
},
|
||||
"cpu_temperature": {
|
||||
"name": "CPU Temperature"
|
||||
"name": "CPU temperature"
|
||||
},
|
||||
"5ghz_2_temperature": {
|
||||
"name": "5GHz Temperature (Radio 2)"
|
||||
"name": "5GHz temperature (Radio 2)"
|
||||
},
|
||||
"6ghz_temperature": {
|
||||
"name": "6GHz Temperature"
|
||||
"name": "6GHz temperature"
|
||||
},
|
||||
"cpu_usage": {
|
||||
"name": "CPU usage"
|
||||
|
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==2.5.7"]
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==2.6.0"]
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_MODE,
|
||||
ATTR_NAME,
|
||||
CONF_ACTIONS,
|
||||
CONF_ALIAS,
|
||||
CONF_CONDITIONS,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -27,6 +28,7 @@ from homeassistant.const import (
|
||||
CONF_MODE,
|
||||
CONF_PATH,
|
||||
CONF_PLATFORM,
|
||||
CONF_TRIGGERS,
|
||||
CONF_VARIABLES,
|
||||
CONF_ZONE,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
@@ -86,11 +88,9 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .config import AutomationConfig, ValidationStatus
|
||||
from .const import (
|
||||
CONF_ACTIONS,
|
||||
CONF_INITIAL_STATE,
|
||||
CONF_TRACE,
|
||||
CONF_TRIGGER_VARIABLES,
|
||||
CONF_TRIGGERS,
|
||||
DEFAULT_INITIAL_STATE,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
|
@@ -14,11 +14,15 @@ from homeassistant.components import blueprint
|
||||
from homeassistant.components.trace import TRACE_CONFIG_SCHEMA
|
||||
from homeassistant.config import config_per_platform, config_without_domain
|
||||
from homeassistant.const import (
|
||||
CONF_ACTION,
|
||||
CONF_ACTIONS,
|
||||
CONF_ALIAS,
|
||||
CONF_CONDITION,
|
||||
CONF_CONDITIONS,
|
||||
CONF_DESCRIPTION,
|
||||
CONF_ID,
|
||||
CONF_TRIGGER,
|
||||
CONF_TRIGGERS,
|
||||
CONF_VARIABLES,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -30,14 +34,10 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.yaml.input import UndefinedSubstitution
|
||||
|
||||
from .const import (
|
||||
CONF_ACTION,
|
||||
CONF_ACTIONS,
|
||||
CONF_HIDE_ENTITY,
|
||||
CONF_INITIAL_STATE,
|
||||
CONF_TRACE,
|
||||
CONF_TRIGGER,
|
||||
CONF_TRIGGER_VARIABLES,
|
||||
CONF_TRIGGERS,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
)
|
||||
@@ -58,34 +58,9 @@ _MINIMAL_PLATFORM_SCHEMA = vol.Schema(
|
||||
def _backward_compat_schema(value: Any | None) -> Any:
|
||||
"""Backward compatibility for automations."""
|
||||
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
|
||||
# `trigger` has been renamed to `triggers`
|
||||
if CONF_TRIGGER in value:
|
||||
if CONF_TRIGGERS in value:
|
||||
raise vol.Invalid(
|
||||
"Cannot specify both 'trigger' and 'triggers'. Please use 'triggers' only."
|
||||
)
|
||||
value[CONF_TRIGGERS] = value.pop(CONF_TRIGGER)
|
||||
|
||||
# `condition` has been renamed to `conditions`
|
||||
if CONF_CONDITION in value:
|
||||
if CONF_CONDITIONS in value:
|
||||
raise vol.Invalid(
|
||||
"Cannot specify both 'condition' and 'conditions'. Please use 'conditions' only."
|
||||
)
|
||||
value[CONF_CONDITIONS] = value.pop(CONF_CONDITION)
|
||||
|
||||
# `action` has been renamed to `actions`
|
||||
if CONF_ACTION in value:
|
||||
if CONF_ACTIONS in value:
|
||||
raise vol.Invalid(
|
||||
"Cannot specify both 'action' and 'actions'. Please use 'actions' only."
|
||||
)
|
||||
value[CONF_ACTIONS] = value.pop(CONF_ACTION)
|
||||
|
||||
return value
|
||||
value = cv.renamed(CONF_TRIGGER, CONF_TRIGGERS)(value)
|
||||
value = cv.renamed(CONF_ACTION, CONF_ACTIONS)(value)
|
||||
return cv.renamed(CONF_CONDITION, CONF_CONDITIONS)(value)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
|
@@ -2,10 +2,6 @@
|
||||
|
||||
import logging
|
||||
|
||||
CONF_ACTION = "action"
|
||||
CONF_ACTIONS = "actions"
|
||||
CONF_TRIGGER = "trigger"
|
||||
CONF_TRIGGERS = "triggers"
|
||||
CONF_TRIGGER_VARIABLES = "trigger_variables"
|
||||
DOMAIN = "automation"
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user