mirror of
https://github.com/home-assistant/core.git
synced 2025-09-20 18:39:40 +00:00
Compare commits
2275 Commits
2024.7.0b4
...
sql-query-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
45aab80b59 | ||
![]() |
611723e44b | ||
![]() |
f8e65c1161 | ||
![]() |
ab064a7f36 | ||
![]() |
4e94ce0cc7 | ||
![]() |
7c6e3fe9c4 | ||
![]() |
7579ebc02a | ||
![]() |
61ac4c7af7 | ||
![]() |
2533bde27a | ||
![]() |
fc1ed7d7cb | ||
![]() |
281a9f042b | ||
![]() |
1d35c745bb | ||
![]() |
51dba1eec3 | ||
![]() |
6e5e96b047 | ||
![]() |
e13f8996f2 | ||
![]() |
7887bcba89 | ||
![]() |
3a92899081 | ||
![]() |
5d64155bb6 | ||
![]() |
6f66f37fc7 | ||
![]() |
890c6e97fd | ||
![]() |
404a7bab18 | ||
![]() |
2337c3ff69 | ||
![]() |
4a6f833fca | ||
![]() |
c541cb5cba | ||
![]() |
df82567356 | ||
![]() |
1bc0ec201a | ||
![]() |
403db6277f | ||
![]() |
4e3bd55c73 | ||
![]() |
ab6d0e3277 | ||
![]() |
480e748b04 | ||
![]() |
b541d214ed | ||
![]() |
0c8ed18ca5 | ||
![]() |
61114d8328 | ||
![]() |
9b9e98a26e | ||
![]() |
60f9fbf800 | ||
![]() |
2ff326b563 | ||
![]() |
266cd623a5 | ||
![]() |
ad362b1d4c | ||
![]() |
47beddc6c6 | ||
![]() |
913e5404da | ||
![]() |
67bc568db6 | ||
![]() |
5f53d3f917 | ||
![]() |
e9798cd1b4 | ||
![]() |
4ef55e5088 | ||
![]() |
9399a54c7a | ||
![]() |
d86b816491 | ||
![]() |
88b95c1236 | ||
![]() |
e56c235424 | ||
![]() |
ec256166cc | ||
![]() |
9de90ca7d5 | ||
![]() |
3e53cc175f | ||
![]() |
b21b300625 | ||
![]() |
52c0a09107 | ||
![]() |
8d97fafb2d | ||
![]() |
fa5f47127c | ||
![]() |
316a57864a | ||
![]() |
a287c8259d | ||
![]() |
c276cfc371 | ||
![]() |
f33328308c | ||
![]() |
1d1a6ee52f | ||
![]() |
d248bf596a | ||
![]() |
ce82c79ff9 | ||
![]() |
d327ec904c | ||
![]() |
b74aced6f3 | ||
![]() |
a1e3e7f24f | ||
![]() |
15976b8207 | ||
![]() |
b1d9e5580c | ||
![]() |
3dc83ef19d | ||
![]() |
14775c822f | ||
![]() |
69a5605551 | ||
![]() |
c2dc4ef215 | ||
![]() |
e6eedc0717 | ||
![]() |
d901cb04b8 | ||
![]() |
f66b539027 | ||
![]() |
d961e20b15 | ||
![]() |
93f791e5d0 | ||
![]() |
db92f29c00 | ||
![]() |
be255613de | ||
![]() |
98a007cb2f | ||
![]() |
b4648136c5 | ||
![]() |
d3deaa6a82 | ||
![]() |
e81aa1cdb2 | ||
![]() |
24f0c88123 | ||
![]() |
b31c6012ae | ||
![]() |
d99f1631ca | ||
![]() |
8260264416 | ||
![]() |
097162eceb | ||
![]() |
d8cbb3540f | ||
![]() |
0f90a3cf60 | ||
![]() |
108a54a4a8 | ||
![]() |
fc767ee562 | ||
![]() |
566c00ef12 | ||
![]() |
407e4f6ca2 | ||
![]() |
254aa8c9ea | ||
![]() |
b4afca3e7e | ||
![]() |
20f7af25e9 | ||
![]() |
69652ca2ca | ||
![]() |
89728f41e1 | ||
![]() |
9283d766d3 | ||
![]() |
bcd92b43ce | ||
![]() |
de82f21446 | ||
![]() |
984a534300 | ||
![]() |
50f3c891fa | ||
![]() |
e3ab30a2a5 | ||
![]() |
f2d41bd99c | ||
![]() |
b53ae884a6 | ||
![]() |
f4997e46fb | ||
![]() |
110ee9ff35 | ||
![]() |
c76d68503a | ||
![]() |
5470d14a11 | ||
![]() |
02139fcca6 | ||
![]() |
16e52f0427 | ||
![]() |
96edaebdd3 | ||
![]() |
8907b7e911 | ||
![]() |
fd0f093e10 | ||
![]() |
e88007af2d | ||
![]() |
a24fdd1c2b | ||
![]() |
f0af33bd2b | ||
![]() |
057f31132b | ||
![]() |
197e65c3a9 | ||
![]() |
d9aa931fac | ||
![]() |
2577fb804b | ||
![]() |
bab930a456 | ||
![]() |
511ec4ba8a | ||
![]() |
013b91394e | ||
![]() |
d9c98316fd | ||
![]() |
88d79d35eb | ||
![]() |
25d33e96cc | ||
![]() |
1fdcbc3f15 | ||
![]() |
05aeb3fbd1 | ||
![]() |
50f9c1e5a4 | ||
![]() |
02b26ac4e6 | ||
![]() |
11d2258afc | ||
![]() |
14c2ca85ec | ||
![]() |
1010edf4bd | ||
![]() |
9e8260736b | ||
![]() |
06d1bbc20f | ||
![]() |
135ebaafa0 | ||
![]() |
ce2ffde22e | ||
![]() |
7d5ddbf51c | ||
![]() |
df58068e84 | ||
![]() |
7d326ff076 | ||
![]() |
49c59339d9 | ||
![]() |
4ab3f1f41f | ||
![]() |
e6c61f207d | ||
![]() |
4e7e896601 | ||
![]() |
55cf3b60eb | ||
![]() |
1a628588b4 | ||
![]() |
ba3872ff87 | ||
![]() |
69843e9ac4 | ||
![]() |
a4fb4e76cb | ||
![]() |
489ceab4b5 | ||
![]() |
1afed8ae15 | ||
![]() |
10c27c3189 | ||
![]() |
975363b660 | ||
![]() |
c8797298ea | ||
![]() |
565f271c5c | ||
![]() |
0c5a3fab22 | ||
![]() |
04b0760e27 | ||
![]() |
e3287b93a5 | ||
![]() |
c4fee124b3 | ||
![]() |
ec0012209d | ||
![]() |
59d72bb4ed | ||
![]() |
3b2893f2f4 | ||
![]() |
63d1cc10e2 | ||
![]() |
d72d4286db | ||
![]() |
23fb4b50c9 | ||
![]() |
08a76133c2 | ||
![]() |
08234efedf | ||
![]() |
1bab373707 | ||
![]() |
85a47ffb68 | ||
![]() |
153eef16bb | ||
![]() |
7efd8089c8 | ||
![]() |
533442f33e | ||
![]() |
a7bca9bcea | ||
![]() |
6c01e4b99c | ||
![]() |
7deb9bf30f | ||
![]() |
1614e2c825 | ||
![]() |
69700f068f | ||
![]() |
91951ed734 | ||
![]() |
24680b731f | ||
![]() |
a8a7d01a84 | ||
![]() |
9b11aaf1eb | ||
![]() |
8a110abc82 | ||
![]() |
e07768412a | ||
![]() |
56b4a7f291 | ||
![]() |
2cd4456762 | ||
![]() |
115c5d1704 | ||
![]() |
06209dd94c | ||
![]() |
c8b0c939e4 | ||
![]() |
e8d57bf636 | ||
![]() |
0cb0af496e | ||
![]() |
ea4443f79e | ||
![]() |
cb8a6af12d | ||
![]() |
14a3217d7e | ||
![]() |
c717e7a6f6 | ||
![]() |
f1b7847d1c | ||
![]() |
69943af68a | ||
![]() |
738cc5095d | ||
![]() |
4cc4ec44b0 | ||
![]() |
0a846cfca8 | ||
![]() |
ea52acd7bd | ||
![]() |
799e95c1bd | ||
![]() |
183c191d63 | ||
![]() |
f3e2d06922 | ||
![]() |
99ab2566c2 | ||
![]() |
0093276e93 | ||
![]() |
461ef33553 | ||
![]() |
723ea6173e | ||
![]() |
1f214bec93 | ||
![]() |
4b62dcfd19 | ||
![]() |
66a8733333 | ||
![]() |
f9ade788eb | ||
![]() |
6d9764185b | ||
![]() |
a944541c58 | ||
![]() |
2f8766a9ec | ||
![]() |
37328c78c1 | ||
![]() |
142469be95 | ||
![]() |
7d552b64f7 | ||
![]() |
65fa4a34ed | ||
![]() |
f581066747 | ||
![]() |
29c0a7f324 | ||
![]() |
bf9d621939 | ||
![]() |
64a68b17f4 | ||
![]() |
46357519e0 | ||
![]() |
24a20c75eb | ||
![]() |
e39bfeac08 | ||
![]() |
874ae15d6a | ||
![]() |
a50aeb0a66 | ||
![]() |
983806817b | ||
![]() |
21c9cd1caa | ||
![]() |
c674a25eba | ||
![]() |
f72d9a2c02 | ||
![]() |
ab163c356f | ||
![]() |
b3399082a8 | ||
![]() |
26e80cec3d | ||
![]() |
f2d39feec0 | ||
![]() |
9b78ae5908 | ||
![]() |
72e235ad9f | ||
![]() |
629b919707 | ||
![]() |
d6d016e029 | ||
![]() |
81c4bb5f72 | ||
![]() |
5836f8edb5 | ||
![]() |
2c3d97d373 | ||
![]() |
dde1ecbf5b | ||
![]() |
ac30efb5ac | ||
![]() |
1f684330e0 | ||
![]() |
b042ebe4ff | ||
![]() |
9911aa4ede | ||
![]() |
667414a457 | ||
![]() |
e6b3d35cdf | ||
![]() |
e6ed3c8c5c | ||
![]() |
392f64d33e | ||
![]() |
aee1be1e64 | ||
![]() |
58851f0048 | ||
![]() |
9c4677a3c6 | ||
![]() |
0790611b93 | ||
![]() |
9b33d2f17e | ||
![]() |
3e967700fd | ||
![]() |
3322fa0294 | ||
![]() |
178482068d | ||
![]() |
5e6f8373e1 | ||
![]() |
5958ef363f | ||
![]() |
5608301178 | ||
![]() |
faacfe3f90 | ||
![]() |
bb88961968 | ||
![]() |
bba298a44d | ||
![]() |
ae6ac31d02 | ||
![]() |
c761d75550 | ||
![]() |
e33a7ecefa | ||
![]() |
04598c6fb1 | ||
![]() |
ff4dac8f3a | ||
![]() |
1227cd8693 | ||
![]() |
c65f845329 | ||
![]() |
e6fc34325d | ||
![]() |
17f0d9ce45 | ||
![]() |
f7e017aa73 | ||
![]() |
3e5d0eb632 | ||
![]() |
fa8f86b672 | ||
![]() |
2c99bd178c | ||
![]() |
99b1fc75d3 | ||
![]() |
78c868c075 | ||
![]() |
1e5762fbf7 | ||
![]() |
7f6bf95aa6 | ||
![]() |
67f761c0e9 | ||
![]() |
324b6529e8 | ||
![]() |
ccde51da85 | ||
![]() |
f6cb28eb5b | ||
![]() |
e050d187c4 | ||
![]() |
ea7e88d000 | ||
![]() |
80f5683cd6 | ||
![]() |
b698dd8f32 | ||
![]() |
3b1b600606 | ||
![]() |
dc2886d9b1 | ||
![]() |
1ddc723274 | ||
![]() |
d4082aee5a | ||
![]() |
903342b394 | ||
![]() |
5f1d7e5566 | ||
![]() |
1af6528f4f | ||
![]() |
f414f5d77a | ||
![]() |
8117532cc7 | ||
![]() |
ac223e64f9 | ||
![]() |
d50bac3b3e | ||
![]() |
6626c63bb5 | ||
![]() |
7fe2f175aa | ||
![]() |
13b071fd72 | ||
![]() |
7ff368fe0d | ||
![]() |
f8879a51fe | ||
![]() |
57902fed22 | ||
![]() |
24a8060f43 | ||
![]() |
165ec62405 | ||
![]() |
a712eca70a | ||
![]() |
36f9b69923 | ||
![]() |
cd382bcdda | ||
![]() |
bd509469ab | ||
![]() |
82c705e188 | ||
![]() |
e1a0a855d5 | ||
![]() |
7063541733 | ||
![]() |
5f967fdee2 | ||
![]() |
b7bbc938d3 | ||
![]() |
29887c2a17 | ||
![]() |
2b6949f3c7 | ||
![]() |
4a6e812963 | ||
![]() |
f8bc662620 | ||
![]() |
f14d5ba5f2 | ||
![]() |
493859e589 | ||
![]() |
ba54a19d4b | ||
![]() |
995ed77849 | ||
![]() |
679baddd3d | ||
![]() |
e8157ed9a2 | ||
![]() |
4cc3f7211b | ||
![]() |
135f15fdc3 | ||
![]() |
04b1d2414d | ||
![]() |
ae74fdf252 | ||
![]() |
f0247e942e | ||
![]() |
2859dde697 | ||
![]() |
2c4b7c2577 | ||
![]() |
b3d1d79a49 | ||
![]() |
71e23e7849 | ||
![]() |
8e0dfbcd13 | ||
![]() |
30994710e6 | ||
![]() |
193a7b7360 | ||
![]() |
e15ac2fbe0 | ||
![]() |
78f7b3340d | ||
![]() |
4ceb9b9dbf | ||
![]() |
3660c2dbb4 | ||
![]() |
2b968dfd9a | ||
![]() |
5f694d9a84 | ||
![]() |
992de497f2 | ||
![]() |
e9682fe003 | ||
![]() |
dc462aa529 | ||
![]() |
5837450a05 | ||
![]() |
f97fc8a907 | ||
![]() |
314ee9c74c | ||
![]() |
a6f3e587bc | ||
![]() |
6317053cc6 | ||
![]() |
04570edb3f | ||
![]() |
86322973d0 | ||
![]() |
6406065e1f | ||
![]() |
a988cd050b | ||
![]() |
dee06d5777 | ||
![]() |
7eccb38851 | ||
![]() |
e06e9bb39c | ||
![]() |
831c28e890 | ||
![]() |
732b4b95db | ||
![]() |
52f52394d5 | ||
![]() |
416d2fb82a | ||
![]() |
b0d1d7bdb2 | ||
![]() |
31dcc6f685 | ||
![]() |
c49a31e0de | ||
![]() |
c5e8710889 | ||
![]() |
6caec89793 | ||
![]() |
5e75c5faff | ||
![]() |
6bde80ad65 | ||
![]() |
bf55cc605a | ||
![]() |
9d67956fc8 | ||
![]() |
b62f216c53 | ||
![]() |
3c864322f7 | ||
![]() |
7cf5d12ec0 | ||
![]() |
b09c6654ec | ||
![]() |
178cb0659a | ||
![]() |
d1dff95ac8 | ||
![]() |
f46fe7eeb2 | ||
![]() |
d8b13c8c02 | ||
![]() |
05c4b1a6a9 | ||
![]() |
ff0a44cc12 | ||
![]() |
a4f0234841 | ||
![]() |
21987a67e7 | ||
![]() |
74a09073c2 | ||
![]() |
138d229fef | ||
![]() |
93eb74d970 | ||
![]() |
b7a0bf152b | ||
![]() |
5fb6c65d23 | ||
![]() |
200f04bf21 | ||
![]() |
26a69458b0 | ||
![]() |
5cb9901134 | ||
![]() |
342ba1b599 | ||
![]() |
7985974a58 | ||
![]() |
2d41723cfe | ||
![]() |
c47fdf7074 | ||
![]() |
4dadf0ea1b | ||
![]() |
81788790df | ||
![]() |
268044cd01 | ||
![]() |
b04e3dc6fd | ||
![]() |
ecd061d46f | ||
![]() |
1c9a1c71d3 | ||
![]() |
efa3f228a5 | ||
![]() |
533e383d5d | ||
![]() |
4639e7d5c7 | ||
![]() |
78b6cdb201 | ||
![]() |
11fd1086af | ||
![]() |
5b5b9ac4ef | ||
![]() |
b20623447e | ||
![]() |
f6e82ae0ba | ||
![]() |
82bedb1ab5 | ||
![]() |
33a22ae208 | ||
![]() |
77e9acd864 | ||
![]() |
b9010e96a0 | ||
![]() |
27d76f5953 | ||
![]() |
64ceb11f8c | ||
![]() |
32d2218ff0 | ||
![]() |
ecc308c326 | ||
![]() |
840d9a0923 | ||
![]() |
81faf1b582 | ||
![]() |
ecf22e4c4f | ||
![]() |
e8d7eb05ae | ||
![]() |
8c5748dcc1 | ||
![]() |
0803ac9b0b | ||
![]() |
8cfac68317 | ||
![]() |
e64ca7c274 | ||
![]() |
0bb8c4832d | ||
![]() |
b15ea58851 | ||
![]() |
86df43879c | ||
![]() |
4527de18d5 | ||
![]() |
bbefe47aeb | ||
![]() |
b19758ff71 | ||
![]() |
401e36b885 | ||
![]() |
6343a086e4 | ||
![]() |
5b6bfa9ac8 | ||
![]() |
e1336a1975 | ||
![]() |
ca34bac479 | ||
![]() |
4a099ab942 | ||
![]() |
4daefe0b6e | ||
![]() |
a040f1a9d1 | ||
![]() |
766733b3b2 | ||
![]() |
b392d61391 | ||
![]() |
be3e720c57 | ||
![]() |
e93d0dfdfc | ||
![]() |
9be8616cc0 | ||
![]() |
7aed35b3f0 | ||
![]() |
f69507527b | ||
![]() |
f53da62026 | ||
![]() |
0558c85b5d | ||
![]() |
ef2ddbf86d | ||
![]() |
778194f7a0 | ||
![]() |
13b12a7657 | ||
![]() |
257742de46 | ||
![]() |
ace6385f5e | ||
![]() |
4f8a6979d9 | ||
![]() |
f02fceed5b | ||
![]() |
cfd2ca3abb | ||
![]() |
9b678e474b | ||
![]() |
5f73c73a88 | ||
![]() |
5f03589d3e | ||
![]() |
089d855c47 | ||
![]() |
750bce2b86 | ||
![]() |
94af95c95b | ||
![]() |
ec9944b92a | ||
![]() |
2b95a642fc | ||
![]() |
eb1c2f5d9f | ||
![]() |
ac28d34ad5 | ||
![]() |
65f33f58e9 | ||
![]() |
86c4ded4cd | ||
![]() |
57da71c537 | ||
![]() |
8e34a0d3c7 | ||
![]() |
1ad1a2d51e | ||
![]() |
acda7bc5c4 | ||
![]() |
618efdb326 | ||
![]() |
b445517244 | ||
![]() |
6e1978971a | ||
![]() |
572293fb8b | ||
![]() |
85cbc2437c | ||
![]() |
228db1c063 | ||
![]() |
97410474f5 | ||
![]() |
e6e985af24 | ||
![]() |
aee5d5126f | ||
![]() |
55eb11055c | ||
![]() |
84d6f5ed07 | ||
![]() |
f8e1c2cfd4 | ||
![]() |
00c1a3fd4e | ||
![]() |
03ba8f6173 | ||
![]() |
69740e865c | ||
![]() |
2343f5e40f | ||
![]() |
634a2b22dc | ||
![]() |
b498c89860 | ||
![]() |
ddc94030a6 | ||
![]() |
c2f2a868c4 | ||
![]() |
60117ae150 | ||
![]() |
a406068f13 | ||
![]() |
baceb2a92a | ||
![]() |
d08f4fbace | ||
![]() |
02a404081a | ||
![]() |
8a8fac46e0 | ||
![]() |
984bbf60ef | ||
![]() |
b7f5236a0a | ||
![]() |
6a89080ce7 | ||
![]() |
7dea5d2fe6 | ||
![]() |
135c80186f | ||
![]() |
2144a9a7b2 | ||
![]() |
86722ba05e | ||
![]() |
be4810731a | ||
![]() |
ac6abb363c | ||
![]() |
5367886732 | ||
![]() |
7a51d4ff62 | ||
![]() |
ef564c537d | ||
![]() |
f8fa6e4309 | ||
![]() |
f1029596d2 | ||
![]() |
4cd3fce555 | ||
![]() |
082290b092 | ||
![]() |
4a212791a2 | ||
![]() |
6bb55ce79e | ||
![]() |
9717a867a7 | ||
![]() |
764166342e | ||
![]() |
45ce0fed0a | ||
![]() |
782ff12e6e | ||
![]() |
af6f78a784 | ||
![]() |
db32460f3b | ||
![]() |
270990fe39 | ||
![]() |
799888df2f | ||
![]() |
933fba84a9 | ||
![]() |
cba6273ac6 | ||
![]() |
a10fed9d72 | ||
![]() |
cc5699bf08 | ||
![]() |
ad674a1c2b | ||
![]() |
b0269faae4 | ||
![]() |
1143efedc5 | ||
![]() |
9e75b63925 | ||
![]() |
940327dccf | ||
![]() |
0270026f7c | ||
![]() |
bf28419851 | ||
![]() |
27b9965b10 | ||
![]() |
be7f3ca439 | ||
![]() |
185b6e5908 | ||
![]() |
7ec0b8b331 | ||
![]() |
95f92ababf | ||
![]() |
89337091b2 | ||
![]() |
78154f5daf | ||
![]() |
f4db9e09c8 | ||
![]() |
b636096ac3 | ||
![]() |
a243ed5b23 | ||
![]() |
3cf3780587 | ||
![]() |
3d0a0cf376 | ||
![]() |
7aae9d9ad3 | ||
![]() |
870bb7efd4 | ||
![]() |
35a6679ae9 | ||
![]() |
a09d0117b1 | ||
![]() |
abaac519c9 | ||
![]() |
f9f3c7fb51 | ||
![]() |
2000db57c8 | ||
![]() |
9414e6d472 | ||
![]() |
4627a565d3 | ||
![]() |
c612cf95a8 | ||
![]() |
1eaaa00687 | ||
![]() |
fe4e6f24f5 | ||
![]() |
f94bf51bb5 | ||
![]() |
260642345d | ||
![]() |
7cb94e6392 | ||
![]() |
e9fe98f7f9 | ||
![]() |
5b2e188b52 | ||
![]() |
c1953e938d | ||
![]() |
9d2c2d90c8 | ||
![]() |
d24a87145d | ||
![]() |
77bcbbcf53 | ||
![]() |
97587fae08 | ||
![]() |
01b54fe1a9 | ||
![]() |
f796950493 | ||
![]() |
495fd946bc | ||
![]() |
6af1e25d7e | ||
![]() |
6d47a4d7e4 | ||
![]() |
fd5533d719 | ||
![]() |
bc380859e8 | ||
![]() |
1fc6ce3acd | ||
![]() |
78d1cd79af | ||
![]() |
a2dd017229 | ||
![]() |
86d8c3b31a | ||
![]() |
bc02925630 | ||
![]() |
b9251e94a9 | ||
![]() |
0d4ca35784 | ||
![]() |
164cfa85da | ||
![]() |
21da79a249 | ||
![]() |
1b73b2a12a | ||
![]() |
54f8f24c2c | ||
![]() |
d530137bec | ||
![]() |
4f722e864c | ||
![]() |
62d38e786d | ||
![]() |
859874487e | ||
![]() |
b16bf29819 | ||
![]() |
6b10dbb38c | ||
![]() |
ea20c4b375 | ||
![]() |
0427aeccb0 | ||
![]() |
4898ba932d | ||
![]() |
5142cb5e98 | ||
![]() |
bb1efe56b6 | ||
![]() |
9c2ba9b157 | ||
![]() |
f51cc8fe12 | ||
![]() |
537d7728a7 | ||
![]() |
844ccf461f | ||
![]() |
94542d42fa | ||
![]() |
b223931ac0 | ||
![]() |
42ab8d0445 | ||
![]() |
ef237a8431 | ||
![]() |
96364f0452 | ||
![]() |
b73ca874bb | ||
![]() |
a6dfa6d4e0 | ||
![]() |
f2d99cb059 | ||
![]() |
35a3d2306c | ||
![]() |
cdb378066c | ||
![]() |
85700fd80f | ||
![]() |
73a2ad7304 | ||
![]() |
f6c4b6b045 | ||
![]() |
0b4d921762 | ||
![]() |
c8a0e5228d | ||
![]() |
832bac8c63 | ||
![]() |
eccce7017f | ||
![]() |
fdb1baadbe | ||
![]() |
7623ee49e4 | ||
![]() |
fa241dcd04 | ||
![]() |
bee77041e8 | ||
![]() |
50b7eb44d1 | ||
![]() |
7b1bf82e3c | ||
![]() |
5b7fd29797 | ||
![]() |
ab811f70b1 | ||
![]() |
67e3139dcf | ||
![]() |
3257bdeed2 | ||
![]() |
1163cc7cab | ||
![]() |
e9e357b12e | ||
![]() |
d246d02ab8 | ||
![]() |
a7fbac5185 | ||
![]() |
b45fe0ec73 | ||
![]() |
d7e3df1974 | ||
![]() |
31fd4efa36 | ||
![]() |
7308912b39 | ||
![]() |
4d103c1fc2 | ||
![]() |
4fd92c17f0 | ||
![]() |
6b7307df81 | ||
![]() |
b09dd95dbd | ||
![]() |
3353c3c205 | ||
![]() |
ccd157dc26 | ||
![]() |
a9d8e47979 | ||
![]() |
30f4d1b958 | ||
![]() |
e682d8c6e2 | ||
![]() |
70704f67d3 | ||
![]() |
232f78e7b6 | ||
![]() |
b6de2cd741 | ||
![]() |
02f81ec481 | ||
![]() |
b7d56ad38a | ||
![]() |
eb5ee1ffd1 | ||
![]() |
cdec43ec06 | ||
![]() |
0fe23c82a4 | ||
![]() |
61cbb77042 | ||
![]() |
bb31fc1ec7 | ||
![]() |
6684f61a54 | ||
![]() |
aa6f0cd55a | ||
![]() |
f6ad018f8f | ||
![]() |
8687c32c15 | ||
![]() |
34b561b211 | ||
![]() |
e32a48ac55 | ||
![]() |
fe82e7f24d | ||
![]() |
433c1a57e7 | ||
![]() |
b36059fc64 | ||
![]() |
13c9d69440 | ||
![]() |
9c7134a865 | ||
![]() |
d7cc2a7e9a | ||
![]() |
f9276e28b0 | ||
![]() |
15ad6db1a7 | ||
![]() |
c1043ada22 | ||
![]() |
d141122008 | ||
![]() |
abeba39842 | ||
![]() |
bb597a908d | ||
![]() |
dcae2f35ce | ||
![]() |
b06a5af069 | ||
![]() |
a624ada8d6 | ||
![]() |
d87366b1e7 | ||
![]() |
5ce8a2d974 | ||
![]() |
a42615add0 | ||
![]() |
ecbff61332 | ||
![]() |
e9bfe82582 | ||
![]() |
55abe68a5f | ||
![]() |
acf523b5fb | ||
![]() |
0216455137 | ||
![]() |
cb37ae6608 | ||
![]() |
3b462906d9 | ||
![]() |
dfb4e9c159 | ||
![]() |
6a6814af61 | ||
![]() |
1a7085b068 | ||
![]() |
804d7aa4c0 | ||
![]() |
1b1d86409c | ||
![]() |
2520fcd284 | ||
![]() |
b0ece4bbaa | ||
![]() |
a18166e3f8 | ||
![]() |
b89a859f14 | ||
![]() |
a4aefe43dc | ||
![]() |
115303faf5 | ||
![]() |
1eadb00fce | ||
![]() |
b609f8e962 | ||
![]() |
e734971d33 | ||
![]() |
b6c9fe86e1 | ||
![]() |
db238a75e3 | ||
![]() |
fb76e70c3f | ||
![]() |
a40dce449f | ||
![]() |
d2dd5ba0e6 | ||
![]() |
3de8828358 | ||
![]() |
b610b29d28 | ||
![]() |
42234e6a09 | ||
![]() |
4a06e20318 | ||
![]() |
ad26db7dc8 | ||
![]() |
5446dd92a9 | ||
![]() |
7670ad0a72 | ||
![]() |
b2d5f9c742 | ||
![]() |
adf8515698 | ||
![]() |
449afe9e6f | ||
![]() |
0058d42ca2 | ||
![]() |
4da385898b | ||
![]() |
8ec8aef02e | ||
![]() |
ed6d6575d7 | ||
![]() |
262d778a38 | ||
![]() |
80aa2c269b | ||
![]() |
a3b5dcc21b | ||
![]() |
fef9c92eb7 | ||
![]() |
4f3d624353 | ||
![]() |
f5e88b8293 | ||
![]() |
faedba0407 | ||
![]() |
adf20b60dc | ||
![]() |
ab522dab71 | ||
![]() |
c2a23bce50 | ||
![]() |
bc91bd3293 | ||
![]() |
6bf59a8dfc | ||
![]() |
cd80cd5caa | ||
![]() |
25d4dd82a0 | ||
![]() |
d16a2fac80 | ||
![]() |
2fd3c42e63 | ||
![]() |
55e5428443 | ||
![]() |
8375b58eac | ||
![]() |
352f0953f3 | ||
![]() |
bdd6f57975 | ||
![]() |
18afe07c16 | ||
![]() |
6baee603a5 | ||
![]() |
5fefa606b6 | ||
![]() |
7bc2381a45 | ||
![]() |
2910369647 | ||
![]() |
17f34b452e | ||
![]() |
220f686078 | ||
![]() |
d878d744e7 | ||
![]() |
b31263b747 | ||
![]() |
4a4209647e | ||
![]() |
9860109db9 | ||
![]() |
b8ac86939b | ||
![]() |
9023d80d1b | ||
![]() |
c702ffa7dd | ||
![]() |
be8186126e | ||
![]() |
4fda025106 | ||
![]() |
c7f863a141 | ||
![]() |
4aacec2de7 | ||
![]() |
a6aae4e857 | ||
![]() |
dde97a02f0 | ||
![]() |
d393317eb2 | ||
![]() |
2f3f124aa1 | ||
![]() |
79a741486c | ||
![]() |
c0fe65fa60 | ||
![]() |
8de0e4ca7c | ||
![]() |
7c179c33b5 | ||
![]() |
177690bcb3 | ||
![]() |
a23b3f84f0 | ||
![]() |
d5388452d4 | ||
![]() |
8a4206da99 | ||
![]() |
f1084a57df | ||
![]() |
0189a05297 | ||
![]() |
7276b4b3ad | ||
![]() |
93bcd413a7 | ||
![]() |
bc25657f0a | ||
![]() |
9db42beade | ||
![]() |
cc1a6d60c0 | ||
![]() |
a913587eb6 | ||
![]() |
ae9e8ca419 | ||
![]() |
69a8c5dc9f | ||
![]() |
3f091470fd | ||
![]() |
c888908cc8 | ||
![]() |
172e2125f6 | ||
![]() |
69f54656c4 | ||
![]() |
c359d4a419 | ||
![]() |
a1b8545568 | ||
![]() |
8c0d9a1320 | ||
![]() |
f764705629 | ||
![]() |
3df78043c0 | ||
![]() |
97de1c2b66 | ||
![]() |
f7f0f49015 | ||
![]() |
a35fa0e95a | ||
![]() |
cddb3bb668 | ||
![]() |
e64e3c2778 | ||
![]() |
5e1cca1c58 | ||
![]() |
2f181cbe41 | ||
![]() |
e706ff0564 | ||
![]() |
8d0e998e54 | ||
![]() |
4f5eab4646 | ||
![]() |
8b96c7873f | ||
![]() |
f14471112d | ||
![]() |
7c7b408df1 | ||
![]() |
3bf00822b0 | ||
![]() |
bf3a2cf393 | ||
![]() |
c8dccec956 | ||
![]() |
ed9c4e0c0d | ||
![]() |
cd552ceb2b | ||
![]() |
c4398efbbb | ||
![]() |
8b1a527602 | ||
![]() |
01f41a597e | ||
![]() |
c32f1efad0 | ||
![]() |
47c96c52b1 | ||
![]() |
dbdb148e12 | ||
![]() |
6a45124878 | ||
![]() |
02d4d1a75b | ||
![]() |
233c04a469 | ||
![]() |
8b4f607806 | ||
![]() |
68f06e63e2 | ||
![]() |
67ed8b207a | ||
![]() |
222011fc5c | ||
![]() |
f6f7459c36 | ||
![]() |
718bc61c88 | ||
![]() |
015a1a6ebc | ||
![]() |
e0a1aaa1b9 | ||
![]() |
7f4dabf546 | ||
![]() |
beb2ef121e | ||
![]() |
5a04d982d9 | ||
![]() |
35bfd0b88f | ||
![]() |
9351f300b0 | ||
![]() |
0d678120e4 | ||
![]() |
5766ea9541 | ||
![]() |
6d8bc84db3 | ||
![]() |
823910b69e | ||
![]() |
aa801d9cc6 | ||
![]() |
067acce4de | ||
![]() |
6999c6b0cf | ||
![]() |
da18aae2d8 | ||
![]() |
6362ca1052 | ||
![]() |
022e1b0c02 | ||
![]() |
94c0b9fc06 | ||
![]() |
5eff4f9816 | ||
![]() |
fb229fcae8 | ||
![]() |
50b35ac4bc | ||
![]() |
ea727546d6 | ||
![]() |
18a7d15d14 | ||
![]() |
be24475cee | ||
![]() |
8066c7dec6 | ||
![]() |
896cd27bea | ||
![]() |
1ffde403f0 | ||
![]() |
b69b927795 | ||
![]() |
6840f27bc6 | ||
![]() |
4a34855a92 | ||
![]() |
b3f7f379df | ||
![]() |
4994e46ad0 | ||
![]() |
1382f7a3dc | ||
![]() |
b973455037 | ||
![]() |
a5136a1021 | ||
![]() |
d9e996def5 | ||
![]() |
224228e448 | ||
![]() |
c8372a3aa5 | ||
![]() |
2135691b90 | ||
![]() |
ea508b2629 | ||
![]() |
09cd79772f | ||
![]() |
7b5db6521c | ||
![]() |
27eba3cd46 | ||
![]() |
41c7414d97 | ||
![]() |
fd7c92879c | ||
![]() |
e7971f5a67 | ||
![]() |
72f9d85bbe | ||
![]() |
956cc6a85c | ||
![]() |
015c50bbdb | ||
![]() |
b6f0893c33 | ||
![]() |
7c92287f97 | ||
![]() |
53a59412bb | ||
![]() |
d78acd480a | ||
![]() |
17930a6d66 | ||
![]() |
d825ac346e | ||
![]() |
fa53055485 | ||
![]() |
b4cba01870 | ||
![]() |
70e368a57e | ||
![]() |
004eccec89 | ||
![]() |
36c01042c1 | ||
![]() |
bd3f0da385 | ||
![]() |
9450744b3b | ||
![]() |
1c03c83c0a | ||
![]() |
fdab23c3f9 | ||
![]() |
7b08e625b4 | ||
![]() |
2102a104d2 | ||
![]() |
3e1aee4cbc | ||
![]() |
b5b01d97f1 | ||
![]() |
02581bbf02 | ||
![]() |
4ac85829c8 | ||
![]() |
ad50136dbd | ||
![]() |
20c4f84a4e | ||
![]() |
bf38db0035 | ||
![]() |
b8c363a82c | ||
![]() |
9393dcddb7 | ||
![]() |
5b434ee336 | ||
![]() |
6ba6334512 | ||
![]() |
8de7a2e3c7 | ||
![]() |
197ac8b950 | ||
![]() |
1958a149c3 | ||
![]() |
7bbbda8d2b | ||
![]() |
5d87a74c3c | ||
![]() |
1f488b00f8 | ||
![]() |
570725293c | ||
![]() |
732b9e47c8 | ||
![]() |
ea75c8864f | ||
![]() |
9514a38320 | ||
![]() |
d94e79d57a | ||
![]() |
02c592d6af | ||
![]() |
d51d584aed | ||
![]() |
e5fd9819da | ||
![]() |
00c3b0d888 | ||
![]() |
aa44c54a19 | ||
![]() |
9940d0281b | ||
![]() |
586a0b12ab | ||
![]() |
75f0384a15 | ||
![]() |
56f51d3e35 | ||
![]() |
7135a919e3 | ||
![]() |
9d6bd359c4 | ||
![]() |
f739644735 | ||
![]() |
b63bc72450 | ||
![]() |
683069cb98 | ||
![]() |
cf20e67f1f | ||
![]() |
74d10b9824 | ||
![]() |
61d4bc1430 | ||
![]() |
07c7bb8b2a | ||
![]() |
9ce7779bde | ||
![]() |
9e10126505 | ||
![]() |
cfef72ae57 | ||
![]() |
86bfc7ada8 | ||
![]() |
0de75aeee1 | ||
![]() |
075550b7ba | ||
![]() |
e5bb1b2cc6 | ||
![]() |
85aca4f095 | ||
![]() |
745eea9a29 | ||
![]() |
d586e7df33 | ||
![]() |
6d4711ce43 | ||
![]() |
8a84addc54 | ||
![]() |
1879db9f8f | ||
![]() |
869ec3f670 | ||
![]() |
70df4ca461 | ||
![]() |
06ee8fdd47 | ||
![]() |
5467685bd8 | ||
![]() |
efaf75f2e6 | ||
![]() |
ca430f0e7b | ||
![]() |
fa61ad072d | ||
![]() |
686598b6b3 | ||
![]() |
5f5dcec0b9 | ||
![]() |
2a5cb8da32 | ||
![]() |
9b497aebb4 | ||
![]() |
5f08883227 | ||
![]() |
4b2073ca59 | ||
![]() |
f98487ef18 | ||
![]() |
dff964582b | ||
![]() |
e5c36c8d56 | ||
![]() |
d765b92cca | ||
![]() |
ba266ab13c | ||
![]() |
d4aa981fd7 | ||
![]() |
ac0d0b21e2 | ||
![]() |
092ab823d1 | ||
![]() |
3ad2456dd9 | ||
![]() |
146ec4e760 | ||
![]() |
f563817b98 | ||
![]() |
ec15a66a68 | ||
![]() |
e708e30c33 | ||
![]() |
383dd80919 | ||
![]() |
6752bd450b | ||
![]() |
b0780e1db5 | ||
![]() |
02a5df0aee | ||
![]() |
cb4a48ca02 | ||
![]() |
64f997718a | ||
![]() |
482cf261c0 | ||
![]() |
1a5706a693 | ||
![]() |
13c320902e | ||
![]() |
bfbd01a4e5 | ||
![]() |
09622e180e | ||
![]() |
84486bad78 | ||
![]() |
c486baccaa | ||
![]() |
57554aba57 | ||
![]() |
c9eb1a2e9c | ||
![]() |
1a64489121 | ||
![]() |
888ffc002f | ||
![]() |
58419f14e8 | ||
![]() |
57a5c7c8b6 | ||
![]() |
b2b40d9ed6 | ||
![]() |
8e578227c3 | ||
![]() |
d3d522c463 | ||
![]() |
53131390ac | ||
![]() |
7820bcf218 | ||
![]() |
49e2bfae31 | ||
![]() |
55a1082866 | ||
![]() |
5bb6272dfa | ||
![]() |
850703824b | ||
![]() |
72fdcd1cb1 | ||
![]() |
33ea67e1d0 | ||
![]() |
047100069b | ||
![]() |
ecadf6a330 | ||
![]() |
b41b7aeb5b | ||
![]() |
c9b81a5c04 | ||
![]() |
51d5e21203 | ||
![]() |
621bd5f0c3 | ||
![]() |
9b4cf873c1 | ||
![]() |
e262f759af | ||
![]() |
78a98afb8d | ||
![]() |
e5f2046b19 | ||
![]() |
d77b5cbbbf | ||
![]() |
5dbd7684ce | ||
![]() |
32a0463f47 | ||
![]() |
62a3902de7 | ||
![]() |
eb3686af06 | ||
![]() |
81983d66f4 | ||
![]() |
e015c0a6ae | ||
![]() |
81c8ba87ab | ||
![]() |
ec957e4a94 | ||
![]() |
08d7beb803 | ||
![]() |
131ce09490 | ||
![]() |
e8eb1ed35c | ||
![]() |
0c7ab2062f | ||
![]() |
f1b933ae0c | ||
![]() |
e6d0bc7d5d | ||
![]() |
e795f81f73 | ||
![]() |
3caffa4dad | ||
![]() |
1f2c54f112 | ||
![]() |
c12a79ecba | ||
![]() |
a94e9d472b | ||
![]() |
cde22a44db | ||
![]() |
33d5ed52e6 | ||
![]() |
78e24be1e7 | ||
![]() |
a89853da9d | ||
![]() |
3b01a57de3 | ||
![]() |
256a2276ef | ||
![]() |
6223fe93c8 | ||
![]() |
7348a1fd0c | ||
![]() |
8687b438f1 | ||
![]() |
4901ecba7f | ||
![]() |
59637d2391 | ||
![]() |
fcccd85ac4 | ||
![]() |
34b32ced25 | ||
![]() |
d7c713d18d | ||
![]() |
4876e35fd8 | ||
![]() |
9198234465 | ||
![]() |
be8e432bea | ||
![]() |
943b1afb55 | ||
![]() |
3e8d3083ac | ||
![]() |
3c4f2c2dcf | ||
![]() |
277883e756 | ||
![]() |
5bda072141 | ||
![]() |
a8e60a6c53 | ||
![]() |
50da3c5c5b | ||
![]() |
c5f9ff6ac5 | ||
![]() |
6393f1f02d | ||
![]() |
c81b9d624b | ||
![]() |
cae992f5e6 | ||
![]() |
9ecdee3b78 | ||
![]() |
c9f0fe3c5d | ||
![]() |
a90d41d9e7 | ||
![]() |
4c7828fd50 | ||
![]() |
99aa68c93f | ||
![]() |
6dd43be6ac | ||
![]() |
67f7e97b4c | ||
![]() |
902bf4ae86 | ||
![]() |
b7b3094a49 | ||
![]() |
e1e64be3c9 | ||
![]() |
f135d3d16c | ||
![]() |
fd6f1cfbdc | ||
![]() |
ac86738572 | ||
![]() |
ad38b9e9e1 | ||
![]() |
e6ef8a34a7 | ||
![]() |
2730713b39 | ||
![]() |
e00f159ebe | ||
![]() |
57c554f516 | ||
![]() |
34f1443a5a | ||
![]() |
b53800a69d | ||
![]() |
1b7fb9ae12 | ||
![]() |
6bdc5be433 | ||
![]() |
d78327a72f | ||
![]() |
0a62a4459f | ||
![]() |
3ba2a0518e | ||
![]() |
d7b0d1a50e | ||
![]() |
42b9c0448c | ||
![]() |
da6a7ebd42 | ||
![]() |
f260d63c58 | ||
![]() |
ff467463f8 | ||
![]() |
5727f30026 | ||
![]() |
32cd54b1e3 | ||
![]() |
51ef5cd3ba | ||
![]() |
07b2a7537b | ||
![]() |
b46b74df90 | ||
![]() |
156a2427ff | ||
![]() |
545514c5cd | ||
![]() |
92acfc1464 | ||
![]() |
73ea62edd4 | ||
![]() |
1fd3c9d6dd | ||
![]() |
0d765a27c9 | ||
![]() |
8d14095cb9 | ||
![]() |
77282ed4b0 | ||
![]() |
632dec614a | ||
![]() |
d3d91a83e5 | ||
![]() |
108dc3795e | ||
![]() |
cd48278671 | ||
![]() |
be4c7291bd | ||
![]() |
4674502b92 | ||
![]() |
4ee256633b | ||
![]() |
0039f1bb49 | ||
![]() |
975cfa6457 | ||
![]() |
5d3c57ecfe | ||
![]() |
f4125eaf4c | ||
![]() |
96de0a4c94 | ||
![]() |
d0ba5534cd | ||
![]() |
9b2118e556 | ||
![]() |
42716723e6 | ||
![]() |
ba276a5cb6 | ||
![]() |
3df6b34a03 | ||
![]() |
ee30510b23 | ||
![]() |
489457c47b | ||
![]() |
a1cdd91d23 | ||
![]() |
fed17a4905 | ||
![]() |
c61efe931a | ||
![]() |
d3df903d1e | ||
![]() |
db6704271c | ||
![]() |
3dc36cf068 | ||
![]() |
20fc5233a1 | ||
![]() |
4c853803f1 | ||
![]() |
19d9a91392 | ||
![]() |
76cd53a864 | ||
![]() |
02c34ba3f8 | ||
![]() |
b14e8d1609 | ||
![]() |
e8b88557ee | ||
![]() |
c73e7ae178 | ||
![]() |
7ec41275d5 | ||
![]() |
debebcfd25 | ||
![]() |
186ca49b16 | ||
![]() |
243a68fb1f | ||
![]() |
bd97a09cae | ||
![]() |
d421525f1b | ||
![]() |
7ec332f857 | ||
![]() |
31d3b3b675 | ||
![]() |
ea94cdb668 | ||
![]() |
cbe94c4706 | ||
![]() |
5612e3a92b | ||
![]() |
33f0840a26 | ||
![]() |
8d538fcd52 | ||
![]() |
9793aa0a5e | ||
![]() |
064d7261b4 | ||
![]() |
0c6dc9e43b | ||
![]() |
c70e611822 | ||
![]() |
02c64c7861 | ||
![]() |
5b32efb6d6 | ||
![]() |
34e72ea16a | ||
![]() |
f30c6e01f9 | ||
![]() |
db9fc27a5c | ||
![]() |
ac1ad9680b | ||
![]() |
4eb096cb0a | ||
![]() |
bc5849e4ef | ||
![]() |
9a3c7111f7 | ||
![]() |
c98c80ce69 | ||
![]() |
453848bcdc | ||
![]() |
7d46890804 | ||
![]() |
7e1fb88e4e | ||
![]() |
94ce02f406 | ||
![]() |
d418a40856 | ||
![]() |
890b54e36f | ||
![]() |
6de824e875 | ||
![]() |
273dc0998f | ||
![]() |
5f4dedb4a8 | ||
![]() |
6f4a8a4a14 | ||
![]() |
39068bb786 | ||
![]() |
7e82b3ecdb | ||
![]() |
8d01ad98eb | ||
![]() |
a78d6b8c36 | ||
![]() |
e8796cd725 | ||
![]() |
b0a4140b4d | ||
![]() |
30373a668c | ||
![]() |
272f0bc21c | ||
![]() |
7f852d0f73 | ||
![]() |
8994c18f73 | ||
![]() |
874b1ae873 | ||
![]() |
7f82fb8cb8 | ||
![]() |
a8cbfe5159 | ||
![]() |
0ab1ccc5ae | ||
![]() |
48661054d9 | ||
![]() |
87e377cf84 | ||
![]() |
8da630f8c6 | ||
![]() |
f629364dc4 | ||
![]() |
fcca475e36 | ||
![]() |
5075f0aac8 | ||
![]() |
537a76d049 | ||
![]() |
b3698a59e1 | ||
![]() |
9b9db86f1c | ||
![]() |
1e28ae49f9 | ||
![]() |
24b6f71f94 | ||
![]() |
ae4360b0e5 | ||
![]() |
769d7214a3 | ||
![]() |
90e7d82049 | ||
![]() |
5e8b022246 | ||
![]() |
43aeaf7a9b | ||
![]() |
63b0feeae7 | ||
![]() |
0f079454bb | ||
![]() |
6be4ef8a1f | ||
![]() |
5fd3b929f4 | ||
![]() |
55abbc51a4 | ||
![]() |
651fb95010 | ||
![]() |
c6713edc8b | ||
![]() |
ee49c57e95 | ||
![]() |
2f47312eeb | ||
![]() |
293ad99dae | ||
![]() |
0fe7aa1a43 | ||
![]() |
b54b08479d | ||
![]() |
ab2f38216d | ||
![]() |
13da20ddf4 | ||
![]() |
436a38c1d2 | ||
![]() |
2b93de1348 | ||
![]() |
ecffae0b4f | ||
![]() |
6f9e39cd3f | ||
![]() |
221480add1 | ||
![]() |
153b69c971 | ||
![]() |
d1d2ce1270 | ||
![]() |
a6068dcdf2 | ||
![]() |
0637e342f6 | ||
![]() |
e9f5c4188e | ||
![]() |
24b12bc509 | ||
![]() |
f0b9a806d1 | ||
![]() |
f8c4ffc060 | ||
![]() |
768d20c645 | ||
![]() |
a0332d049b | ||
![]() |
4ee2c445d1 | ||
![]() |
458c81cdae | ||
![]() |
8e024ad20f | ||
![]() |
a46fffd550 | ||
![]() |
288faf48e7 | ||
![]() |
7e0970e917 | ||
![]() |
e6e748ae0a | ||
![]() |
75b1700ed3 | ||
![]() |
267dfac737 | ||
![]() |
099110767a | ||
![]() |
cafff3eddf | ||
![]() |
c0732fbb1d | ||
![]() |
7b5b6c7b85 | ||
![]() |
a08ffdc8d3 | ||
![]() |
1ef4332af6 | ||
![]() |
d0d2fd7918 | ||
![]() |
c518c4756b | ||
![]() |
a3a99cc631 | ||
![]() |
977a55e3b8 | ||
![]() |
002db3c3e9 | ||
![]() |
d9e44bab69 | ||
![]() |
4b93fc61b5 | ||
![]() |
214b5efd72 | ||
![]() |
9bd822d693 | ||
![]() |
bf89eaae25 | ||
![]() |
f9b359ae30 | ||
![]() |
24ed003471 | ||
![]() |
a835750252 | ||
![]() |
ad07bdb62b | ||
![]() |
41104324ec | ||
![]() |
e9344ae101 | ||
![]() |
56a9167ed2 | ||
![]() |
e0b90c4b36 | ||
![]() |
976902f22c | ||
![]() |
0f69c58ba9 | ||
![]() |
1e6c96c6eb | ||
![]() |
63b14d14c1 | ||
![]() |
6aaaba6419 | ||
![]() |
3b8e736fe3 | ||
![]() |
68841b3d8a | ||
![]() |
3d8afe7cb8 | ||
![]() |
8595242142 | ||
![]() |
ebe7bc0686 | ||
![]() |
4ab180f016 | ||
![]() |
372649069e | ||
![]() |
98df46f3ea | ||
![]() |
269fb23527 | ||
![]() |
ad5cbf0da6 | ||
![]() |
10cdf64f90 | ||
![]() |
ec8e639804 | ||
![]() |
37f37f7287 | ||
![]() |
ef7d68bfd6 | ||
![]() |
12ec66c2c2 | ||
![]() |
72d37036b9 | ||
![]() |
e029dad0eb | ||
![]() |
0cde518a89 | ||
![]() |
dab66990c0 | ||
![]() |
d3029af888 | ||
![]() |
419bf0165a | ||
![]() |
978ee918cb | ||
![]() |
8bca9a3449 | ||
![]() |
87ecf5d85e | ||
![]() |
2f8dfb424b | ||
![]() |
53c85a5c9b | ||
![]() |
281c66b6c2 | ||
![]() |
6788c43775 | ||
![]() |
f006716173 | ||
![]() |
de5b5f6d36 | ||
![]() |
e9ea90dc82 | ||
![]() |
0be68dcd7f | ||
![]() |
c92d9dcb74 | ||
![]() |
2b486c3bd5 | ||
![]() |
c28a138dfe | ||
![]() |
3ddcffb7b7 | ||
![]() |
8cb7e9785f | ||
![]() |
16434b5306 | ||
![]() |
c1c5cff993 | ||
![]() |
4620a54582 | ||
![]() |
0b691f9393 | ||
![]() |
bcf4c73f32 | ||
![]() |
ca4c617d4b | ||
![]() |
172778053c | ||
![]() |
f5f9480b5a | ||
![]() |
e50802aca3 | ||
![]() |
de18be235d | ||
![]() |
7810dc213a | ||
![]() |
ba5e3ca44b | ||
![]() |
e2c6b7915e | ||
![]() |
5b4dd07189 | ||
![]() |
362c772d67 | ||
![]() |
339b5117c5 | ||
![]() |
c8a6c6a5c1 | ||
![]() |
53870617e8 | ||
![]() |
24144c5855 | ||
![]() |
978de5b8b0 | ||
![]() |
1e59ce2909 | ||
![]() |
fb5443fe2f | ||
![]() |
dae23a8153 | ||
![]() |
a2c2488c8b | ||
![]() |
474e8b7a43 | ||
![]() |
243c16d194 | ||
![]() |
a0a5f640dc | ||
![]() |
d2cc25cee6 | ||
![]() |
cf0aef079b | ||
![]() |
6d725b5e34 | ||
![]() |
3d3bc1cab1 | ||
![]() |
bf0e5baa76 | ||
![]() |
f479b64ff9 | ||
![]() |
ec937781ca | ||
![]() |
37426f7366 | ||
![]() |
d983e3b25d | ||
![]() |
58d4e72996 | ||
![]() |
02bb1ec8e7 | ||
![]() |
f551130d65 | ||
![]() |
42610f4e09 | ||
![]() |
41d75e159b | ||
![]() |
b06d3fe3b7 | ||
![]() |
e4ef4b81ba | ||
![]() |
0927dd9090 | ||
![]() |
e2276458ed | ||
![]() |
454ca0ce95 | ||
![]() |
4ae6e38800 | ||
![]() |
55cee89392 | ||
![]() |
fa0a5451b9 | ||
![]() |
52b90621c7 | ||
![]() |
843fae825f | ||
![]() |
e6dec7c856 | ||
![]() |
7a4e40ade0 | ||
![]() |
10c084c6e0 | ||
![]() |
8ae4c4445d | ||
![]() |
07ceafed62 | ||
![]() |
1e8da192b6 | ||
![]() |
57e233d30f | ||
![]() |
efb7bede40 | ||
![]() |
a0f91d27a3 | ||
![]() |
385f5be7e8 | ||
![]() |
7bf4e4779c | ||
![]() |
6921e053e4 | ||
![]() |
7f76de2c7a | ||
![]() |
35f84f32d6 | ||
![]() |
054242ff0f | ||
![]() |
14ec7e5f76 | ||
![]() |
0aea9b0edb | ||
![]() |
1b6f880a27 | ||
![]() |
d0c1632976 | ||
![]() |
dc9b25af1c | ||
![]() |
0ad2398bda | ||
![]() |
89198b0512 | ||
![]() |
8d290dd9d6 | ||
![]() |
9970b7eece | ||
![]() |
baa97ca981 | ||
![]() |
a22e12dac9 | ||
![]() |
d8440e809a | ||
![]() |
c860b6cd4b | ||
![]() |
aeabe3ab95 | ||
![]() |
4bcdb551d4 | ||
![]() |
b122c2932a | ||
![]() |
8082808ec2 | ||
![]() |
3a0b3a4589 | ||
![]() |
f8ba4d41ba | ||
![]() |
336740dbfa | ||
![]() |
ea29c2c4d4 | ||
![]() |
dbe68ca057 | ||
![]() |
62cc868150 | ||
![]() |
3304e18316 | ||
![]() |
6645932fb7 | ||
![]() |
bc7d2d2195 | ||
![]() |
22d977acb0 | ||
![]() |
234870538d | ||
![]() |
2f0870a063 | ||
![]() |
2160f981c2 | ||
![]() |
e01c605d04 | ||
![]() |
d81f6fa8f2 | ||
![]() |
d0233d01da | ||
![]() |
2723ab3b27 | ||
![]() |
260e98c3f7 | ||
![]() |
a9bf12f102 | ||
![]() |
190f12cead | ||
![]() |
026538b6dc | ||
![]() |
e9d4c03300 | ||
![]() |
74f04a1ede | ||
![]() |
33cb674a4b | ||
![]() |
d7e33429e3 | ||
![]() |
77f10a23b0 | ||
![]() |
c6a0c20c64 | ||
![]() |
99f01ac61e | ||
![]() |
894f3fe439 | ||
![]() |
df00e1a65b | ||
![]() |
ef7a84d3f4 | ||
![]() |
3b95e88ff7 | ||
![]() |
6109b9206c | ||
![]() |
50751574b4 | ||
![]() |
9f53d0ccd9 | ||
![]() |
ab945ee76e | ||
![]() |
6d8f99903d | ||
![]() |
73b836df55 | ||
![]() |
73f6e3c07b | ||
![]() |
19d2d023ab | ||
![]() |
1d62f0e380 | ||
![]() |
342e6a503a | ||
![]() |
c044417837 | ||
![]() |
3111951757 | ||
![]() |
a34858a567 | ||
![]() |
62613af033 | ||
![]() |
2dec7136c8 | ||
![]() |
662760360a | ||
![]() |
9e259cbf2d | ||
![]() |
5f33e85b30 | ||
![]() |
162b734be7 | ||
![]() |
a2fab98358 | ||
![]() |
0d27cdc845 | ||
![]() |
a8321fac95 | ||
![]() |
726fcb485d | ||
![]() |
2e71c8d43b | ||
![]() |
a67b6c5ba6 | ||
![]() |
8a52ffb967 | ||
![]() |
156e92a4cd | ||
![]() |
5f02f3caa9 | ||
![]() |
62b9b34561 | ||
![]() |
df85067cae | ||
![]() |
a0d0e0f4a9 | ||
![]() |
244af091b8 | ||
![]() |
f260b048d6 | ||
![]() |
ebd50d327b | ||
![]() |
6a86cdf3f5 | ||
![]() |
1acbcf921a | ||
![]() |
12384104f4 | ||
![]() |
3ef1e5816e | ||
![]() |
48978fb7f6 | ||
![]() |
97aaae1372 | ||
![]() |
cdf7ecebc0 | ||
![]() |
cdb26ef8a3 | ||
![]() |
76d6a1fe13 | ||
![]() |
df8c949884 | ||
![]() |
adb88f0165 | ||
![]() |
c84fe04d6b | ||
![]() |
86db976ed7 | ||
![]() |
e269ff617c | ||
![]() |
414aee395e | ||
![]() |
3be95ebc87 | ||
![]() |
ede130aa53 | ||
![]() |
21d35ff311 | ||
![]() |
8fb700c30e | ||
![]() |
2148cfc899 | ||
![]() |
c40a9ac4b5 | ||
![]() |
ab7c4244d2 | ||
![]() |
f40897b54c | ||
![]() |
73475aa675 | ||
![]() |
c223709c7c | ||
![]() |
52454f5218 | ||
![]() |
f94b28f72d | ||
![]() |
acb4a92628 | ||
![]() |
f8015a7abc | ||
![]() |
4c972694cd | ||
![]() |
c6f1ec34e2 | ||
![]() |
43596f22a4 | ||
![]() |
67fee5be31 | ||
![]() |
c89de2e6a6 | ||
![]() |
307ae53066 | ||
![]() |
322553b8a9 | ||
![]() |
fc770434f1 | ||
![]() |
56b6747bc0 | ||
![]() |
ea5eb0f8f2 | ||
![]() |
bb81cfa57a | ||
![]() |
22c89356c0 | ||
![]() |
385576bfb2 | ||
![]() |
90af40b5c4 | ||
![]() |
417abda649 | ||
![]() |
0462582da3 | ||
![]() |
2fce71ea52 | ||
![]() |
61111f5d71 | ||
![]() |
70f05e5f13 | ||
![]() |
311b1e236a | ||
![]() |
e0c7073da1 | ||
![]() |
088717926d | ||
![]() |
664c4e6189 | ||
![]() |
d87bbaa67a | ||
![]() |
af6c28983d | ||
![]() |
9fcfc6d9f3 | ||
![]() |
e56f8d795f | ||
![]() |
7ed5feee92 | ||
![]() |
f3fe61f929 | ||
![]() |
4fd5ced1f5 | ||
![]() |
fce68018b7 | ||
![]() |
abeac3f3aa | ||
![]() |
924e767736 | ||
![]() |
3142f52a79 | ||
![]() |
a9c9963f0f | ||
![]() |
71e5ffb2bd | ||
![]() |
319fdf42c3 | ||
![]() |
1e0c03257c | ||
![]() |
5dbf907323 | ||
![]() |
cd4358ed9e | ||
![]() |
865190f0b0 | ||
![]() |
0e0a339517 | ||
![]() |
020961d2d8 | ||
![]() |
c81d5a1ac2 | ||
![]() |
8b729e3c7d | ||
![]() |
02b12837d4 | ||
![]() |
e812b0e02f | ||
![]() |
b0837dd98f | ||
![]() |
43806553fc | ||
![]() |
f762359abf | ||
![]() |
058b012e6c | ||
![]() |
0213f1c5c0 | ||
![]() |
71370758a8 | ||
![]() |
38a44676eb | ||
![]() |
05ce3d35b3 | ||
![]() |
2151086b0a | ||
![]() |
9c83af3789 | ||
![]() |
ac3eecc879 | ||
![]() |
ec0910e3da | ||
![]() |
fd0c26cd56 | ||
![]() |
a4c5dee082 | ||
![]() |
37c09dbdb6 | ||
![]() |
73d1973625 | ||
![]() |
5a04a886cf | ||
![]() |
50802f84f0 | ||
![]() |
138b68ecc0 | ||
![]() |
e0b01ee94e | ||
![]() |
4f2c3df518 | ||
![]() |
51a6bb1c22 | ||
![]() |
6bf9ec69f3 | ||
![]() |
21309eeb5d | ||
![]() |
0a1b46c52f | ||
![]() |
9512f9eec3 | ||
![]() |
ab94422c18 | ||
![]() |
ec105e5265 | ||
![]() |
cadd8521ae | ||
![]() |
8825c50671 | ||
![]() |
a72cc3c248 | ||
![]() |
780f7254c1 | ||
![]() |
37621e77ae | ||
![]() |
8017386c73 | ||
![]() |
a5f4c25a2c | ||
![]() |
1d7bddf449 | ||
![]() |
711bdaf373 | ||
![]() |
803d9c5a8e | ||
![]() |
1133c41fa8 | ||
![]() |
a06af7ee93 | ||
![]() |
c54717707e | ||
![]() |
440d83d754 | ||
![]() |
42dcd693d5 | ||
![]() |
1925614a14 | ||
![]() |
42003ae5ac | ||
![]() |
33df857283 | ||
![]() |
6702d232e2 | ||
![]() |
e9b7cc1eba | ||
![]() |
65e03f952a | ||
![]() |
30b5297fde | ||
![]() |
86f7718c1f | ||
![]() |
d10aa6656b | ||
![]() |
ff59adc661 | ||
![]() |
5d809f88ba | ||
![]() |
8b0f28e7f4 | ||
![]() |
7a337ecd1d | ||
![]() |
fb758bd8b6 | ||
![]() |
6f15352eda | ||
![]() |
bf09cee66f | ||
![]() |
0aa6a17da8 | ||
![]() |
4245357403 | ||
![]() |
72c3c0eb53 | ||
![]() |
8d0d8fd006 | ||
![]() |
9120115577 | ||
![]() |
805d4fcc40 | ||
![]() |
92e6dacf14 | ||
![]() |
d783813ba0 | ||
![]() |
5e56c27703 | ||
![]() |
9d2cf70ba5 | ||
![]() |
c5ced14473 | ||
![]() |
bff663b3c1 | ||
![]() |
f14777d0ae | ||
![]() |
a4980cf0fd | ||
![]() |
31dc80c616 | ||
![]() |
898803abe9 | ||
![]() |
5b25c24539 | ||
![]() |
e44f00cf7c | ||
![]() |
154da1b18b | ||
![]() |
69ed730101 | ||
![]() |
01861cd240 | ||
![]() |
4498bf9ec4 | ||
![]() |
4a22b95620 | ||
![]() |
592cfc9c17 | ||
![]() |
d4cc44d352 | ||
![]() |
ba0cd595ff | ||
![]() |
9ca398ef82 | ||
![]() |
7aada39b77 | ||
![]() |
a1fae0e0ce | ||
![]() |
cfabb778dd | ||
![]() |
48d9df74ed | ||
![]() |
b07fcb6a4a | ||
![]() |
3ea694a069 | ||
![]() |
9321bba61e | ||
![]() |
c13d5a663a | ||
![]() |
6e85f09722 | ||
![]() |
45843a3112 | ||
![]() |
2a31774f91 | ||
![]() |
f801649e9c | ||
![]() |
60e1df2a87 | ||
![]() |
5dbaaa9068 | ||
![]() |
10f3813ffd | ||
![]() |
71300b197a | ||
![]() |
b754f03c11 | ||
![]() |
e11d24f06f | ||
![]() |
5f088e0501 | ||
![]() |
93cf8c1311 | ||
![]() |
3e8335f61b | ||
![]() |
5f909d0c52 | ||
![]() |
98651cbd2e | ||
![]() |
19e272a901 | ||
![]() |
10ee554f1f | ||
![]() |
f7825eb5b1 | ||
![]() |
8ab6a505a4 | ||
![]() |
bd4ef4b2ff | ||
![]() |
242b28377d | ||
![]() |
b048ad8afe | ||
![]() |
148803460b | ||
![]() |
c4c623ea99 | ||
![]() |
4d68ae1199 | ||
![]() |
3059bf0536 | ||
![]() |
1aa9588dcb | ||
![]() |
00aafc0cf7 | ||
![]() |
6350c5479b | ||
![]() |
ad47a7b8c6 | ||
![]() |
617db25efb | ||
![]() |
ea65ff5876 | ||
![]() |
cf4bd7fd1c | ||
![]() |
e30f315565 | ||
![]() |
cd478c356e | ||
![]() |
27a2114ba9 | ||
![]() |
4cf733b9a2 | ||
![]() |
8a28cbe9e2 | ||
![]() |
6129a37bb0 | ||
![]() |
0558e480ce | ||
![]() |
f126360c67 | ||
![]() |
833ac4db49 | ||
![]() |
72458d143d | ||
![]() |
12228d8a00 | ||
![]() |
8110b60fc9 | ||
![]() |
cd72bdd851 | ||
![]() |
26bc37195e | ||
![]() |
2def33b168 | ||
![]() |
a5c9aa44bd | ||
![]() |
751935539a | ||
![]() |
866cdcc993 | ||
![]() |
eb0f6f7088 | ||
![]() |
fb8eeac563 | ||
![]() |
1fefd396b9 | ||
![]() |
67facdf3a5 | ||
![]() |
27975c7517 | ||
![]() |
792c6a9cd9 | ||
![]() |
b6609fa77c | ||
![]() |
a66631f1ee | ||
![]() |
de8415d5c7 | ||
![]() |
0d48d5a5ec | ||
![]() |
76bdc4f5c4 | ||
![]() |
6937aed9fe | ||
![]() |
959298d121 | ||
![]() |
9b4e64ab62 | ||
![]() |
275a7499b1 | ||
![]() |
e2141dc208 | ||
![]() |
790d22dc46 | ||
![]() |
37ffe09add | ||
![]() |
dc5ae9e0b2 | ||
![]() |
4c1cb6cce8 | ||
![]() |
643dcb1429 | ||
![]() |
e9b62dd203 | ||
![]() |
fc6906a12f | ||
![]() |
846528131e | ||
![]() |
1c2e6ab3db | ||
![]() |
8e45eb6168 | ||
![]() |
d95b28bdee | ||
![]() |
17172d841c | ||
![]() |
5ea1601c2c | ||
![]() |
44f989ae38 | ||
![]() |
f1693f2fde | ||
![]() |
52de706a85 | ||
![]() |
6e8cfb3376 | ||
![]() |
ac8ea8356c | ||
![]() |
f115efe51d | ||
![]() |
99476d9337 | ||
![]() |
98370fd9a0 | ||
![]() |
d113ea8e47 | ||
![]() |
ec536bda3d | ||
![]() |
490dd53edf | ||
![]() |
131d9ec51b | ||
![]() |
48145c1a7d | ||
![]() |
cf34b46b5a | ||
![]() |
3c14aa12ab | ||
![]() |
df9ced9768 | ||
![]() |
b5a0e89c03 | ||
![]() |
f46c127423 | ||
![]() |
e04d612874 | ||
![]() |
2bc7904b51 | ||
![]() |
be0cf545b2 | ||
![]() |
8f7c3da456 | ||
![]() |
17daccd38a | ||
![]() |
8ff4991f07 | ||
![]() |
43481ffeac | ||
![]() |
ac8bbe9db4 | ||
![]() |
696ae91c35 | ||
![]() |
f29094f41d | ||
![]() |
294c31f6f5 | ||
![]() |
cce77affc7 | ||
![]() |
1ec95f900d | ||
![]() |
a358609609 | ||
![]() |
18370bdbb4 | ||
![]() |
e86f2220d0 | ||
![]() |
9f839dbb77 | ||
![]() |
1b8944dab6 | ||
![]() |
9d204613e8 | ||
![]() |
f2c9188e46 | ||
![]() |
1561324274 | ||
![]() |
34b5df50cc | ||
![]() |
130dbcb3f1 | ||
![]() |
89ffee9ad5 | ||
![]() |
94db251aea | ||
![]() |
ad2d794fd6 | ||
![]() |
178655647d | ||
![]() |
6e5c3904b6 | ||
![]() |
a773d0c8f6 | ||
![]() |
6eeb70151d | ||
![]() |
fd815beb2d | ||
![]() |
1cf62916a7 | ||
![]() |
8c459959ee | ||
![]() |
4b4b17e774 | ||
![]() |
9383920b1a | ||
![]() |
ffc39585ed | ||
![]() |
45ab9cae1a | ||
![]() |
bf8cef3337 | ||
![]() |
324755b8f5 | ||
![]() |
e3958d4adb | ||
![]() |
94d010a4c0 | ||
![]() |
4289efb506 | ||
![]() |
b9cfd4e8ce | ||
![]() |
7332bc5faf | ||
![]() |
641d6382fd | ||
![]() |
288c297c9c | ||
![]() |
e4731c07cb | ||
![]() |
dfccd4abf9 | ||
![]() |
994d6f552c | ||
![]() |
b015611a2a | ||
![]() |
f4e362c5d0 | ||
![]() |
a542236614 | ||
![]() |
651439ea06 | ||
![]() |
eda450838e | ||
![]() |
b906daa493 | ||
![]() |
ac668dce7d | ||
![]() |
1bb4d62a3e | ||
![]() |
0b970f9a85 | ||
![]() |
d2b695e7b5 | ||
![]() |
b2f23c1a5e | ||
![]() |
f403afb012 | ||
![]() |
ee276aff44 | ||
![]() |
0acd1dc5d1 | ||
![]() |
21815e1621 | ||
![]() |
15933bb16f | ||
![]() |
930cd0dc50 | ||
![]() |
fc4af48179 | ||
![]() |
ba1cf84ea5 | ||
![]() |
59cf01e252 | ||
![]() |
46e681f4fc | ||
![]() |
d42dced852 | ||
![]() |
d0c10c961d | ||
![]() |
0088765268 | ||
![]() |
afb184db73 | ||
![]() |
eb5a98e7ea | ||
![]() |
2ab02c06c6 | ||
![]() |
0cf5b4f965 | ||
![]() |
ad02afe7be | ||
![]() |
daaf35d4c1 | ||
![]() |
229e54d0b1 | ||
![]() |
97de9c9f69 | ||
![]() |
700675042b | ||
![]() |
1c1e1a7bfa | ||
![]() |
98dfb47448 | ||
![]() |
1b42b32ac1 | ||
![]() |
dbe98de82a | ||
![]() |
b28f528a7a | ||
![]() |
d3f424227f | ||
![]() |
22718ca32a | ||
![]() |
2b9bddc3fc | ||
![]() |
213bbae63c | ||
![]() |
def27a082c | ||
![]() |
c3830a58cc | ||
![]() |
b054c037fe | ||
![]() |
dcef25c0fa | ||
![]() |
5a24ee0bc0 | ||
![]() |
e71f6c5948 | ||
![]() |
cdb2ec4231 | ||
![]() |
adee8094e7 | ||
![]() |
6c42596bdd | ||
![]() |
b3a62a97b4 | ||
![]() |
62d9020261 | ||
![]() |
e47cbf3cf7 | ||
![]() |
d799a4575b | ||
![]() |
b406438fa7 | ||
![]() |
b14f22926a | ||
![]() |
276f6c7ee7 | ||
![]() |
83fac6192d | ||
![]() |
84a8259103 | ||
![]() |
3ca66be268 | ||
![]() |
001bb3a04e | ||
![]() |
50cc31e9cc | ||
![]() |
0afebf744f | ||
![]() |
df7be501d3 | ||
![]() |
ebe7a4747d | ||
![]() |
10d3c3d341 | ||
![]() |
6ab6ce30ef | ||
![]() |
fe0bafd067 | ||
![]() |
04a6285e62 | ||
![]() |
e07bf61f03 | ||
![]() |
df1b02d44e | ||
![]() |
79f4cc9c12 | ||
![]() |
7d5341cab2 | ||
![]() |
d5135d4956 | ||
![]() |
950c72a04c | ||
![]() |
6df15ad8fc | ||
![]() |
a1e6f8c2ec | ||
![]() |
092e362f01 | ||
![]() |
28f06cb5a0 | ||
![]() |
411630429d | ||
![]() |
9a1f7f020c | ||
![]() |
d12f2384c5 | ||
![]() |
24c82c2475 | ||
![]() |
b949240d4a | ||
![]() |
f78933235d | ||
![]() |
ece8b74967 | ||
![]() |
1d8382a498 | ||
![]() |
873d96bab3 | ||
![]() |
31ed32da6c | ||
![]() |
f1d6ad9073 | ||
![]() |
d1264655a0 | ||
![]() |
ad1ba1a5e5 | ||
![]() |
255778d0c7 | ||
![]() |
3c69301365 | ||
![]() |
67a4c2c884 | ||
![]() |
d429bcef16 | ||
![]() |
02e7707f91 | ||
![]() |
ee2df9c4b3 | ||
![]() |
0be1f773a2 | ||
![]() |
a6f6221f16 | ||
![]() |
4958e8e5c1 | ||
![]() |
8aedb1201d | ||
![]() |
aa74ad0061 | ||
![]() |
43e4223a8e | ||
![]() |
869f24df49 | ||
![]() |
cf96084ea3 | ||
![]() |
1eec49696a | ||
![]() |
1f22f0d89b | ||
![]() |
dd8ba0828a | ||
![]() |
c9acd1711c | ||
![]() |
4589be2d11 | ||
![]() |
d55d02623a | ||
![]() |
24f6e6e885 | ||
![]() |
ac9c08f52c | ||
![]() |
8a5b201d75 | ||
![]() |
0e9acf2685 | ||
![]() |
3b023367d7 | ||
![]() |
3dbab1a580 | ||
![]() |
1144e23d8d | ||
![]() |
e8ef2c2822 | ||
![]() |
a4d4fc6827 | ||
![]() |
7002042119 | ||
![]() |
c59fc4e3c7 | ||
![]() |
cc2782edc7 | ||
![]() |
b6a23fad35 | ||
![]() |
cfef09d653 | ||
![]() |
84d8bc711d | ||
![]() |
595e688c56 | ||
![]() |
408e524551 | ||
![]() |
73716ea529 | ||
![]() |
c9240b8e34 | ||
![]() |
7958c0825e | ||
![]() |
291f309c0e | ||
![]() |
f85c356522 | ||
![]() |
8017bc6776 | ||
![]() |
2040c285b1 | ||
![]() |
53767b6159 | ||
![]() |
4881205831 | ||
![]() |
5029da6919 | ||
![]() |
61f1c8d963 | ||
![]() |
48172b0426 | ||
![]() |
2b64f6f2ab | ||
![]() |
1080a4ef1e | ||
![]() |
44c89e6c3b | ||
![]() |
d94b36cfbb | ||
![]() |
85168239cd | ||
![]() |
547b24ce58 | ||
![]() |
e8bcb3e11e | ||
![]() |
c89a9b5ce0 | ||
![]() |
13631250b4 | ||
![]() |
6621cf475a | ||
![]() |
36e74cd9a6 | ||
![]() |
16827ea09e | ||
![]() |
c4956b66b0 | ||
![]() |
e4a0a21b67 | ||
![]() |
bc363c385f | ||
![]() |
7a9792c111 | ||
![]() |
1a715d7b89 | ||
![]() |
46a488d871 | ||
![]() |
0b76d5c9ca | ||
![]() |
355c7399d7 | ||
![]() |
e26b4554e6 | ||
![]() |
976cb434c9 | ||
![]() |
f284aa41eb | ||
![]() |
1332e39f9e | ||
![]() |
8709c668cc | ||
![]() |
87f7703f3c | ||
![]() |
e7ffd7b9ad | ||
![]() |
9b2233e65e | ||
![]() |
fbb98a668c | ||
![]() |
a885bdfe76 | ||
![]() |
ac57eb7614 | ||
![]() |
84204c38be | ||
![]() |
febd1a3772 | ||
![]() |
1665cb40ac | ||
![]() |
c33cbf8312 | ||
![]() |
23e061ccbd | ||
![]() |
399548a973 | ||
![]() |
a4d889e958 | ||
![]() |
510315732a | ||
![]() |
476efb1d36 | ||
![]() |
bdc6805771 | ||
![]() |
1b9f27fab7 | ||
![]() |
d1e76d5c3c | ||
![]() |
4377f4cbea | ||
![]() |
7d31d553d0 | ||
![]() |
9749cf113a | ||
![]() |
0e52d149e4 | ||
![]() |
6b045a7d7b | ||
![]() |
1fa6972a66 | ||
![]() |
b3e833f677 | ||
![]() |
807ed0ce10 | ||
![]() |
5cb41106b5 | ||
![]() |
98a2e46d4a | ||
![]() |
24afbde79e | ||
![]() |
65d2ca53cb | ||
![]() |
23b905b422 | ||
![]() |
de458493f8 | ||
![]() |
efd3252849 | ||
![]() |
3b6acd5380 | ||
![]() |
1e6dc74812 | ||
![]() |
0d0ca22103 | ||
![]() |
52627b9aed | ||
![]() |
ba7e45e157 | ||
![]() |
592ef59c5a | ||
![]() |
195f07a18a | ||
![]() |
5b39989869 | ||
![]() |
b8b7c23258 | ||
![]() |
faf43ed4c7 | ||
![]() |
bd234db48f | ||
![]() |
2edb7eb42c | ||
![]() |
72d706ab52 | ||
![]() |
e322cada48 | ||
![]() |
baf2ebf1f2 | ||
![]() |
326d24d78b | ||
![]() |
3adea1ada9 | ||
![]() |
02dffcde1a | ||
![]() |
cdc3897319 | ||
![]() |
22f5f59478 | ||
![]() |
76a62028ad | ||
![]() |
6fd1f0a34f | ||
![]() |
e544550380 | ||
![]() |
8819a9aa52 | ||
![]() |
1f6744847d | ||
![]() |
4a8436d6bc | ||
![]() |
b13e78f3a3 | ||
![]() |
71b7ee40e5 | ||
![]() |
9ca9377cad | ||
![]() |
e3516be3e3 | ||
![]() |
fac511aa46 | ||
![]() |
3df3e6d081 | ||
![]() |
07d80d5ad9 | ||
![]() |
2d054fb5df | ||
![]() |
2635573bbc | ||
![]() |
90d622cd02 | ||
![]() |
0ffebd4853 | ||
![]() |
383de96549 | ||
![]() |
690164a518 | ||
![]() |
167a8c6613 | ||
![]() |
74687f3b60 | ||
![]() |
2f307d6a8a | ||
![]() |
d8f55763c5 | ||
![]() |
4b2be448f0 | ||
![]() |
8a7e2c05a5 | ||
![]() |
887ab1dc58 | ||
![]() |
a787ce8633 | ||
![]() |
88ed43c779 | ||
![]() |
16d7764f18 | ||
![]() |
a0f8012f48 | ||
![]() |
5a052feb87 | ||
![]() |
779a7ddaa2 | ||
![]() |
a9740faeda | ||
![]() |
3a0e85beb8 | ||
![]() |
c19fb35d02 | ||
![]() |
6f716c1753 | ||
![]() |
40384b9acd | ||
![]() |
3bbf8df6d6 | ||
![]() |
07f095aa42 | ||
![]() |
c2dc9e9b67 | ||
![]() |
b5367e5994 | ||
![]() |
1209abc944 | ||
![]() |
a29dc4ef1e | ||
![]() |
7a3039aecb | ||
![]() |
d506c30b38 | ||
![]() |
a0b604f98c | ||
![]() |
001ee0cc0b | ||
![]() |
e5c7ff6a5b | ||
![]() |
918ac5d67c | ||
![]() |
afb0a6e0ab | ||
![]() |
b3a50893cf | ||
![]() |
c6cfe073ea | ||
![]() |
d53cfbbb4e | ||
![]() |
8354aa434e | ||
![]() |
5ce54c2174 | ||
![]() |
813fee663e | ||
![]() |
dcf4e91234 | ||
![]() |
361e81821c | ||
![]() |
52b743e88a | ||
![]() |
788d1999ff | ||
![]() |
c4903dd982 | ||
![]() |
8a23e37837 | ||
![]() |
77fc1c991c | ||
![]() |
ce54ca9c8e | ||
![]() |
c8bb64882e | ||
![]() |
2815c43f3e | ||
![]() |
38aa6bcf19 | ||
![]() |
2506acc095 | ||
![]() |
c9911fa8ce | ||
![]() |
bc3562a9e8 | ||
![]() |
5513682de4 | ||
![]() |
61b2e4ca32 | ||
![]() |
546d6b22f1 | ||
![]() |
414525503c | ||
![]() |
4cc414fbf8 | ||
![]() |
09ff44e59e | ||
![]() |
44640ef9e8 | ||
![]() |
f08638eead | ||
![]() |
921430d497 | ||
![]() |
2f0dd6f704 | ||
![]() |
f11b316dac | ||
![]() |
ca55986057 | ||
![]() |
aa5ebaf613 | ||
![]() |
88583149ea | ||
![]() |
d5d77db4f9 | ||
![]() |
295cfd26aa | ||
![]() |
74c2f000d8 | ||
![]() |
269b8b07c4 | ||
![]() |
6af9527310 | ||
![]() |
eceecbb07b | ||
![]() |
14af3661f3 | ||
![]() |
af733425c2 | ||
![]() |
4fc89e8861 | ||
![]() |
bcec268c04 | ||
![]() |
becf9fcce2 | ||
![]() |
ad9e0ef8e4 | ||
![]() |
f58eafe8fc | ||
![]() |
a7246400b3 | ||
![]() |
38a30b343d | ||
![]() |
1a63bb89cb | ||
![]() |
2f5ec41fa6 | ||
![]() |
f672eec515 | ||
![]() |
419d89f863 | ||
![]() |
05ffd637f5 | ||
![]() |
b375f5227b | ||
![]() |
8b3319b772 | ||
![]() |
289a630578 | ||
![]() |
bb62a8a7dc | ||
![]() |
e961ddd5fe | ||
![]() |
be68255ca4 | ||
![]() |
57aced50aa | ||
![]() |
5deb69d492 | ||
![]() |
255cc9ed74 | ||
![]() |
7aca7cf858 | ||
![]() |
ca7fb906cc | ||
![]() |
d15d001cfc | ||
![]() |
d55be79e6a | ||
![]() |
bf608691d5 | ||
![]() |
75e3afd369 | ||
![]() |
72fdebeb88 | ||
![]() |
f65304957a | ||
![]() |
5280291f98 | ||
![]() |
7172d798f8 | ||
![]() |
e0b0959b3f | ||
![]() |
bcee5f4d9f | ||
![]() |
559caf4179 | ||
![]() |
b5f1076bb2 | ||
![]() |
8f98fb2ec4 | ||
![]() |
08a0eaf184 | ||
![]() |
3ee8f6edba | ||
![]() |
e866417c01 | ||
![]() |
05c63eb884 | ||
![]() |
bb52bfd73d | ||
![]() |
7319492bf3 | ||
![]() |
66932e3d9a | ||
![]() |
0ec07001bd | ||
![]() |
0dcfd38cdc | ||
![]() |
b45eff9a2b | ||
![]() |
ec577c7bd3 | ||
![]() |
723c4a1eb5 | ||
![]() |
b30b4d5a3a | ||
![]() |
8165acddeb | ||
![]() |
0f3ed3bb67 | ||
![]() |
d1a96ef362 | ||
![]() |
917eeba984 | ||
![]() |
59bb8b360e | ||
![]() |
6028e5b77a | ||
![]() |
83df470307 | ||
![]() |
20ac0aa7b1 | ||
![]() |
f57c942901 | ||
![]() |
8994ab1686 | ||
![]() |
b350ba9657 | ||
![]() |
5fd589053a | ||
![]() |
2d5961fa4f | ||
![]() |
25932dff28 | ||
![]() |
0ab7647fea | ||
![]() |
ce34a5e495 | ||
![]() |
7d8cbbaacb | ||
![]() |
f07f9062c1 | ||
![]() |
5a6deddd30 | ||
![]() |
c5804d362c | ||
![]() |
852bb19223 | ||
![]() |
6ed0960648 | ||
![]() |
8f6addcc61 | ||
![]() |
2cfd6d53bd | ||
![]() |
0dfb5bd7d9 | ||
![]() |
d4ecbc91c3 | ||
![]() |
04ab74589a | ||
![]() |
5995459de5 | ||
![]() |
ba38f2e43b | ||
![]() |
cef7def024 | ||
![]() |
8ed11d4b90 | ||
![]() |
b03c10647e | ||
![]() |
a6454cf3c7 | ||
![]() |
3549aaf69c | ||
![]() |
a3394675f3 | ||
![]() |
97ef56d264 | ||
![]() |
4fb0621027 | ||
![]() |
c029c534d6 | ||
![]() |
f4224a0327 | ||
![]() |
3e8773c0d5 | ||
![]() |
0ea1677f51 | ||
![]() |
e907c45981 | ||
![]() |
a8f4684929 | ||
![]() |
d03a6f84a3 | ||
![]() |
9505fcdd7d | ||
![]() |
3e3ab7a134 | ||
![]() |
b56c4a757c | ||
![]() |
984bbf8851 | ||
![]() |
1fdd056c0e | ||
![]() |
c7906f90a3 | ||
![]() |
d427dff68d | ||
![]() |
f69b850b1a | ||
![]() |
d0ce0d562e | ||
![]() |
2e031d0194 | ||
![]() |
d2a457c24f | ||
![]() |
c385deb6a3 | ||
![]() |
6ef8e87f88 | ||
![]() |
4437c4a204 | ||
![]() |
23e5e25149 | ||
![]() |
c13786c952 | ||
![]() |
6f8c9c28e7 | ||
![]() |
0fdf037ba0 | ||
![]() |
3d580259e1 | ||
![]() |
6d93695e2c | ||
![]() |
540da3cac6 | ||
![]() |
6286177041 | ||
![]() |
9b980602c9 | ||
![]() |
1e72c2f94d | ||
![]() |
5e39faa9f8 | ||
![]() |
84de2da19f | ||
![]() |
03c6e0c55f | ||
![]() |
c98e70a6dc | ||
![]() |
827bfa89b3 | ||
![]() |
e0e744aed7 | ||
![]() |
ec069f9084 | ||
![]() |
4e34d02d2d | ||
![]() |
e764afecac | ||
![]() |
f3761a8e53 | ||
![]() |
bccd5c8c35 | ||
![]() |
440928bcbe | ||
![]() |
338687522a | ||
![]() |
6421612429 | ||
![]() |
c4ab0dcd74 | ||
![]() |
195643d916 | ||
![]() |
aaef31958b | ||
![]() |
629dab238f | ||
![]() |
c419d226d5 | ||
![]() |
f4b76406f2 | ||
![]() |
eb2d2ce1b3 | ||
![]() |
89dfca962f | ||
![]() |
d423dae8ac | ||
![]() |
f3c76cd698 | ||
![]() |
0c910bc000 | ||
![]() |
b9e01b9253 | ||
![]() |
e446875c7e | ||
![]() |
3895252965 | ||
![]() |
9758b08036 | ||
![]() |
970dd99226 | ||
![]() |
0d53ce4fb8 | ||
![]() |
8de771de96 | ||
![]() |
1f54180807 | ||
![]() |
9f6783dcf5 | ||
![]() |
cb92510571 | ||
![]() |
a93855ded3 | ||
![]() |
a165064e9d | ||
![]() |
54a5a3e3fb | ||
![]() |
9aa2cc11e9 | ||
![]() |
4f7c6bdce8 | ||
![]() |
022f545342 | ||
![]() |
06f495dd45 | ||
![]() |
85629dc31e | ||
![]() |
5634741ce2 | ||
![]() |
3e9b57cc07 | ||
![]() |
c9c573dbce | ||
![]() |
5503379a3b | ||
![]() |
617ab48fa9 | ||
![]() |
28e72753ad | ||
![]() |
a5a631148e | ||
![]() |
f189d87fe9 | ||
![]() |
5e1c8b0c54 | ||
![]() |
2449e2029e | ||
![]() |
32e64f7911 | ||
![]() |
b7a65d9a82 | ||
![]() |
9d7078e1fa | ||
![]() |
1973c604b6 | ||
![]() |
6bceb8ec48 | ||
![]() |
fcfb580f0c | ||
![]() |
516b9126b7 | ||
![]() |
2146a4729b | ||
![]() |
9b2915efed | ||
![]() |
dd6cc82f70 | ||
![]() |
8839a71adc | ||
![]() |
f1a57d69cc | ||
![]() |
702d53ca30 | ||
![]() |
4c6cbadc11 | ||
![]() |
d8ab2debfd | ||
![]() |
31e9de3b95 | ||
![]() |
ed1eb8ac9c | ||
![]() |
d09a274548 | ||
![]() |
02b142fbde | ||
![]() |
33b4f40b2a |
@@ -49,6 +49,7 @@ base_platforms: &base_platforms
|
||||
- homeassistant/components/tts/**
|
||||
- homeassistant/components/update/**
|
||||
- homeassistant/components/vacuum/**
|
||||
- homeassistant/components/valve/**
|
||||
- homeassistant/components/water_heater/**
|
||||
- homeassistant/components/weather/**
|
||||
|
||||
@@ -145,6 +146,7 @@ requirements: &requirements
|
||||
- homeassistant/package_constraints.txt
|
||||
- requirements*.txt
|
||||
- pyproject.toml
|
||||
- script/licenses.py
|
||||
|
||||
any:
|
||||
- *base_platforms
|
||||
|
1738
.coveragerc
1738
.coveragerc
File diff suppressed because it is too large
Load Diff
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -74,7 +74,6 @@ If the code communicates with devices, web services, or third-party tools:
|
||||
- [ ] New or updated dependencies have been added to `requirements_all.txt`.
|
||||
Updated by running `python3 -m script.gen_requirements_all`.
|
||||
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
|
||||
- [ ] Untested files have been added to `.coveragerc`.
|
||||
|
||||
<!--
|
||||
This project is very active and we have a high turnover of pull requests.
|
||||
|
26
.github/workflows/builder.yml
vendored
26
.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.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
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.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
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.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
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.2.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2024.03.5
|
||||
uses: home-assistant/builder@2024.08.2
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -256,14 +256,14 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.2.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2024.03.5
|
||||
uses: home-assistant/builder@2024.08.2
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -323,20 +323,20 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.5.0
|
||||
uses: sigstore/cosign-installer@v3.6.0
|
||||
with:
|
||||
cosign-release: "v2.2.3"
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: matrix.registry == 'docker.io/homeassistant'
|
||||
uses: docker/login-action@v3.2.0
|
||||
uses: docker/login-action@v3.3.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.2.0
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -453,12 +453,12 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
- name: Download translations
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: translations
|
||||
|
||||
|
168
.github/workflows/ci.yaml
vendored
168
.github/workflows/ci.yaml
vendored
@@ -31,12 +31,16 @@ on:
|
||||
description: "Only run mypy"
|
||||
default: false
|
||||
type: boolean
|
||||
audit-licenses-only:
|
||||
description: "Only run audit licenses"
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 9
|
||||
CACHE_VERSION: 10
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 8
|
||||
HA_SHORT_VERSION: "2024.7"
|
||||
HA_SHORT_VERSION: "2024.9"
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
ALL_PYTHON_VERSIONS: "['3.12']"
|
||||
# 10.3 is the oldest supported version
|
||||
@@ -86,7 +90,7 @@ jobs:
|
||||
tests_glob: ${{ steps.info.outputs.tests_glob }}
|
||||
tests: ${{ steps.info.outputs.tests }}
|
||||
skip_coverage: ${{ steps.info.outputs.skip_coverage }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
@@ -218,10 +222,11 @@ jobs:
|
||||
|
||||
pre-commit:
|
||||
name: Prepare pre-commit base
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
steps:
|
||||
@@ -229,7 +234,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -266,7 +271,7 @@ jobs:
|
||||
|
||||
lint-ruff-format:
|
||||
name: Check ruff-format
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pre-commit
|
||||
@@ -274,7 +279,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
@@ -306,7 +311,7 @@ jobs:
|
||||
|
||||
lint-ruff:
|
||||
name: Check ruff
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pre-commit
|
||||
@@ -314,7 +319,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
@@ -343,9 +348,10 @@ jobs:
|
||||
pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure
|
||||
env:
|
||||
RUFF_OUTPUT_FORMAT: github
|
||||
|
||||
lint-other:
|
||||
name: Check other linters
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pre-commit
|
||||
@@ -353,7 +359,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
@@ -437,7 +443,7 @@ jobs:
|
||||
|
||||
base:
|
||||
name: Prepare dependencies
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs: info
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
@@ -448,7 +454,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -508,25 +514,31 @@ jobs:
|
||||
uv pip install -U "pip>=21.3.1" setuptools wheel
|
||||
uv pip install -r requirements.txt
|
||||
python -m script.gen_requirements_all ci
|
||||
uv pip install -r requirements_all_pytest.txt
|
||||
uv pip install -r requirements_test.txt
|
||||
uv pip install -r requirements_all_pytest.txt -r requirements_test.txt
|
||||
uv pip install -e . --config-settings editable_mode=compat
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
steps:
|
||||
- name: Install additional OS dependencies
|
||||
run: |
|
||||
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install \
|
||||
libturbojpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -546,10 +558,11 @@ jobs:
|
||||
|
||||
gen-requirements-all:
|
||||
name: Check all requirements
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
@@ -558,7 +571,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -576,12 +589,56 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.gen_requirements_all validate
|
||||
|
||||
audit-licenses:
|
||||
name: Audit licenses
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- base
|
||||
if: |
|
||||
(github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
|| github.event.inputs.audit-licenses-only == 'true')
|
||||
&& needs.info.outputs.requirements == 'true'
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.1
|
||||
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.0.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Run pip-licenses
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pip-licenses --format=json --output-file=licenses.json
|
||||
- name: Upload licenses
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: licenses
|
||||
path: licenses.json
|
||||
- name: Process licenses
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python -m script.licenses
|
||||
|
||||
pylint:
|
||||
name: Check pylint
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
if: |
|
||||
github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
|| github.event.inputs.pylint-only == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -591,7 +648,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -623,10 +680,12 @@ jobs:
|
||||
|
||||
pylint-tests:
|
||||
name: Check pylint on tests
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
if: |
|
||||
(github.event.inputs.mypy-only != 'true' || github.event.inputs.pylint-only == 'true')
|
||||
(github.event.inputs.mypy-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
|| github.event.inputs.pylint-only == 'true')
|
||||
&& (needs.info.outputs.tests_glob || needs.info.outputs.test_full_suite == 'true')
|
||||
needs:
|
||||
- info
|
||||
@@ -636,7 +695,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -657,20 +716,21 @@ jobs:
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint --ignore-missing-annotations=y tests
|
||||
pylint tests
|
||||
- name: Run pylint (partially)
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
shell: bash
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pylint --ignore-missing-annotations=y tests/components/${{ needs.info.outputs.tests_glob }}
|
||||
pylint tests/components/${{ needs.info.outputs.tests_glob }}
|
||||
|
||||
mypy:
|
||||
name: Check mypy
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
if: |
|
||||
github.event.inputs.pylint-only != 'true'
|
||||
&& github.event.inputs.audit-licenses-only != 'true'
|
||||
|| github.event.inputs.mypy-only == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -680,7 +740,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -729,12 +789,13 @@ jobs:
|
||||
mypy homeassistant/components/${{ needs.info.outputs.integrations_glob }}
|
||||
|
||||
prepare-pytest-full:
|
||||
runs-on: ubuntu-22.04
|
||||
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.test_full_suite == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -754,7 +815,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -772,19 +833,20 @@ 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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: pytest_buckets
|
||||
path: pytest_buckets.txt
|
||||
overwrite: true
|
||||
|
||||
pytest-full:
|
||||
runs-on: ubuntu-22.04
|
||||
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.test_full_suite == 'true'
|
||||
needs:
|
||||
- info
|
||||
@@ -817,7 +879,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -836,7 +898,7 @@ jobs:
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||
- name: Download pytest_buckets
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: pytest_buckets
|
||||
- name: Compile English translations
|
||||
@@ -858,6 +920,7 @@ jobs:
|
||||
cov_params+=(--cov-report=xml)
|
||||
fi
|
||||
|
||||
echo "Test group ${{ matrix.group }}: $(sed -n "${{ matrix.group }},1p" pytest_buckets.txt)"
|
||||
python3 -b -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
@@ -871,14 +934,14 @@ 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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
@@ -890,7 +953,7 @@ jobs:
|
||||
./script/check_dirty
|
||||
|
||||
pytest-mariadb:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
services:
|
||||
mariadb:
|
||||
image: ${{ matrix.mariadb-group }}
|
||||
@@ -904,6 +967,7 @@ jobs:
|
||||
&& 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.mariadb_groups != '[]'
|
||||
needs:
|
||||
- info
|
||||
@@ -935,7 +999,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -996,7 +1060,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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
@@ -1004,7 +1068,7 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
@@ -1029,6 +1093,7 @@ jobs:
|
||||
&& 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.postgresql_groups != '[]'
|
||||
needs:
|
||||
- info
|
||||
@@ -1060,7 +1125,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -1122,7 +1187,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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
@@ -1130,7 +1195,7 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
@@ -1143,7 +1208,7 @@ jobs:
|
||||
coverage-full:
|
||||
name: Upload test coverage to Codecov (full suite)
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pytest-full
|
||||
@@ -1154,7 +1219,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
@@ -1167,12 +1232,13 @@ jobs:
|
||||
version: v0.6.0
|
||||
|
||||
pytest-partial:
|
||||
runs-on: ubuntu-22.04
|
||||
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.tests_glob
|
||||
&& needs.info.outputs.test_full_suite == 'false'
|
||||
needs:
|
||||
@@ -1205,7 +1271,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -1263,14 +1329,14 @@ 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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
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.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
@@ -1282,7 +1348,7 @@ jobs:
|
||||
coverage-partial:
|
||||
name: Upload test coverage to Codecov (partial suite)
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- info
|
||||
- pytest-partial
|
||||
@@ -1291,7 +1357,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
|
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.25.10
|
||||
uses: github/codeql-action/init@v3.26.4
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.25.10
|
||||
uses: github/codeql-action/analyze@v3.26.4
|
||||
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.1.7
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
36
.github/workflows/wheels.yml
vendored
36
.github/workflows/wheels.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
uses: actions/setup-python@v5.1.1
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -82,14 +82,14 @@ jobs:
|
||||
) > .env_file
|
||||
|
||||
- name: Upload env_file
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: env_file
|
||||
path: ./.env_file
|
||||
overwrite: true
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: requirements_diff
|
||||
path: ./requirements_diff.txt
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
python -m script.gen_requirements_all ci
|
||||
|
||||
- name: Upload requirements_all_wheels
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: requirements_all_wheels
|
||||
path: ./requirements_all_wheels_*.txt
|
||||
@@ -121,17 +121,17 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -159,17 +159,17 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Download requirements_all_wheels
|
||||
uses: actions/download-artifact@v4.1.7
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
name: requirements_all_wheels
|
||||
|
||||
@@ -203,7 +203,7 @@ jobs:
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
- name: Build wheels (old cython)
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -211,14 +211,14 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic;pymicro-vad
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_old-cython.txt"
|
||||
pip: "'cython<3'"
|
||||
|
||||
- name: Build wheels (part 1)
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -226,13 +226,13 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic;pymicro-vad
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtaa"
|
||||
|
||||
- name: Build wheels (part 2)
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -240,13 +240,13 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic;pymicro-vad
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtab"
|
||||
|
||||
- name: Build wheels (part 3)
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -254,7 +254,7 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;SQLAlchemy;protobuf;pydantic;pymicro-vad
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtac"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.9
|
||||
rev: v0.6.2
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
@@ -12,7 +12,7 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
args:
|
||||
- --ignore-words-list=astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn,pres,ser,ue
|
||||
- --ignore-words-list=astroid,checkin,currenty,hass,iif,incomfort,lookin,nam,NotIn
|
||||
- --skip="./.*,*.csv,*.json,*.ambr"
|
||||
- --quiet-level=2
|
||||
exclude_types: [csv, json, html]
|
||||
@@ -83,7 +83,7 @@ repos:
|
||||
pass_filenames: false
|
||||
language: script
|
||||
types: [text]
|
||||
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|\.coveragerc|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements_test.txt)$
|
||||
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements_test.txt)$
|
||||
- id: hassfest-metadata
|
||||
name: hassfest-metadata
|
||||
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata
|
||||
|
@@ -21,6 +21,7 @@ homeassistant.helpers.entity_platform
|
||||
homeassistant.helpers.entity_values
|
||||
homeassistant.helpers.event
|
||||
homeassistant.helpers.reload
|
||||
homeassistant.helpers.script
|
||||
homeassistant.helpers.script_variables
|
||||
homeassistant.helpers.singleton
|
||||
homeassistant.helpers.sun
|
||||
@@ -94,9 +95,8 @@ homeassistant.components.aruba.*
|
||||
homeassistant.components.arwn.*
|
||||
homeassistant.components.aseko_pool_live.*
|
||||
homeassistant.components.assist_pipeline.*
|
||||
homeassistant.components.asterisk_cdr.*
|
||||
homeassistant.components.asterisk_mbox.*
|
||||
homeassistant.components.asuswrt.*
|
||||
homeassistant.components.autarco.*
|
||||
homeassistant.components.auth.*
|
||||
homeassistant.components.automation.*
|
||||
homeassistant.components.awair.*
|
||||
@@ -118,6 +118,7 @@ homeassistant.components.bond.*
|
||||
homeassistant.components.braviatv.*
|
||||
homeassistant.components.brother.*
|
||||
homeassistant.components.browser.*
|
||||
homeassistant.components.bryant_evolution.*
|
||||
homeassistant.components.bthome.*
|
||||
homeassistant.components.button.*
|
||||
homeassistant.components.calendar.*
|
||||
@@ -165,6 +166,7 @@ homeassistant.components.ecowitt.*
|
||||
homeassistant.components.efergy.*
|
||||
homeassistant.components.electrasmart.*
|
||||
homeassistant.components.electric_kiwi.*
|
||||
homeassistant.components.elevenlabs.*
|
||||
homeassistant.components.elgato.*
|
||||
homeassistant.components.elkm1.*
|
||||
homeassistant.components.emulated_hue.*
|
||||
@@ -194,7 +196,9 @@ homeassistant.components.fritzbox.*
|
||||
homeassistant.components.fritzbox_callmonitor.*
|
||||
homeassistant.components.fronius.*
|
||||
homeassistant.components.frontend.*
|
||||
homeassistant.components.fujitsu_fglair.*
|
||||
homeassistant.components.fully_kiosk.*
|
||||
homeassistant.components.fyta.*
|
||||
homeassistant.components.generic_hygrostat.*
|
||||
homeassistant.components.generic_thermostat.*
|
||||
homeassistant.components.geo_location.*
|
||||
@@ -253,6 +257,7 @@ homeassistant.components.integration.*
|
||||
homeassistant.components.intent.*
|
||||
homeassistant.components.intent_script.*
|
||||
homeassistant.components.ios.*
|
||||
homeassistant.components.iotty.*
|
||||
homeassistant.components.ipp.*
|
||||
homeassistant.components.iqvia.*
|
||||
homeassistant.components.islamic_prayer_times.*
|
||||
@@ -277,6 +282,7 @@ homeassistant.components.lidarr.*
|
||||
homeassistant.components.lifx.*
|
||||
homeassistant.components.light.*
|
||||
homeassistant.components.linear_garage_door.*
|
||||
homeassistant.components.linkplay.*
|
||||
homeassistant.components.litejet.*
|
||||
homeassistant.components.litterrobot.*
|
||||
homeassistant.components.local_ip.*
|
||||
@@ -287,7 +293,9 @@ homeassistant.components.logger.*
|
||||
homeassistant.components.london_underground.*
|
||||
homeassistant.components.lookin.*
|
||||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.madvr.*
|
||||
homeassistant.components.mailbox.*
|
||||
homeassistant.components.manual.*
|
||||
homeassistant.components.map.*
|
||||
homeassistant.components.mastodon.*
|
||||
homeassistant.components.matrix.*
|
||||
@@ -382,6 +390,7 @@ homeassistant.components.samsungtv.*
|
||||
homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.script.*
|
||||
homeassistant.components.search.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.sensibo.*
|
||||
|
42
.vscode/launch.json
vendored
42
.vscode/launch.json
vendored
@@ -6,38 +6,52 @@
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Home Assistant",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "homeassistant",
|
||||
"justMyCode": false,
|
||||
"args": ["--debug", "-c", "config"],
|
||||
"args": [
|
||||
"--debug",
|
||||
"-c",
|
||||
"config"
|
||||
],
|
||||
"preLaunchTask": "Compile English translations"
|
||||
},
|
||||
{
|
||||
"name": "Home Assistant (skip pip)",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "homeassistant",
|
||||
"justMyCode": false,
|
||||
"args": ["--debug", "-c", "config", "--skip-pip"],
|
||||
"args": [
|
||||
"--debug",
|
||||
"-c",
|
||||
"config",
|
||||
"--skip-pip"
|
||||
],
|
||||
"preLaunchTask": "Compile English translations"
|
||||
},
|
||||
{
|
||||
"name": "Home Assistant: Changed tests",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "pytest",
|
||||
"justMyCode": false,
|
||||
"args": ["--timeout=10", "--picked"],
|
||||
"args": [
|
||||
"--timeout=10",
|
||||
"--picked"
|
||||
],
|
||||
},
|
||||
{
|
||||
// Debug by attaching to local Home Assistant server using Remote Python Debugger.
|
||||
// See https://www.home-assistant.io/integrations/debugpy/
|
||||
"name": "Home Assistant: Attach Local",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"port": 5678,
|
||||
"host": "localhost",
|
||||
"connect": {
|
||||
"port": 5678,
|
||||
"host": "localhost"
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
@@ -49,10 +63,12 @@
|
||||
// Debug by attaching to remote Home Assistant server using Remote Python Debugger.
|
||||
// See https://www.home-assistant.io/integrations/debugpy/
|
||||
"name": "Home Assistant: Attach Remote",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"port": 5678,
|
||||
"host": "homeassistant.local",
|
||||
"connect": {
|
||||
"port": 5678,
|
||||
"host": "homeassistant.local"
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
@@ -61,4 +77,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
1
.vscode/tasks.json
vendored
1
.vscode/tasks.json
vendored
@@ -76,6 +76,7 @@
|
||||
"detail": "Generate code coverage report for a given integration.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
|
||||
"dependsOn": ["Compile English translations"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
|
77
CODEOWNERS
77
CODEOWNERS
@@ -80,8 +80,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/airzone/ @Noltari
|
||||
/homeassistant/components/airzone_cloud/ @Noltari
|
||||
/tests/components/airzone_cloud/ @Noltari
|
||||
/homeassistant/components/aladdin_connect/ @swcloudgenie
|
||||
/tests/components/aladdin_connect/ @swcloudgenie
|
||||
/homeassistant/components/alarm_control_panel/ @home-assistant/core
|
||||
/tests/components/alarm_control_panel/ @home-assistant/core
|
||||
/homeassistant/components/alert/ @home-assistant/core @frenck
|
||||
@@ -110,6 +108,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/anova/ @Lash-L
|
||||
/homeassistant/components/anthemav/ @hyralex
|
||||
/tests/components/anthemav/ @hyralex
|
||||
/homeassistant/components/anthropic/ @Shulyaka
|
||||
/tests/components/anthropic/ @Shulyaka
|
||||
/homeassistant/components/aosmith/ @bdr99
|
||||
/tests/components/aosmith/ @bdr99
|
||||
/homeassistant/components/apache_kafka/ @bachya
|
||||
@@ -157,6 +157,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/aurora_abb_powerone/ @davet2001
|
||||
/homeassistant/components/aussie_broadband/ @nickw444 @Bre77
|
||||
/tests/components/aussie_broadband/ @nickw444 @Bre77
|
||||
/homeassistant/components/autarco/ @klaasnicolaas
|
||||
/tests/components/autarco/ @klaasnicolaas
|
||||
/homeassistant/components/auth/ @home-assistant/core
|
||||
/tests/components/auth/ @home-assistant/core
|
||||
/homeassistant/components/automation/ @home-assistant/core
|
||||
@@ -197,7 +199,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/bluemaestro/ @bdraco
|
||||
/homeassistant/components/blueprint/ @home-assistant/core
|
||||
/tests/components/blueprint/ @home-assistant/core
|
||||
/homeassistant/components/bluesound/ @thrawnarn
|
||||
/homeassistant/components/bluesound/ @thrawnarn @LouisChrist
|
||||
/tests/components/bluesound/ @thrawnarn @LouisChrist
|
||||
/homeassistant/components/bluetooth/ @bdraco
|
||||
/tests/components/bluetooth/ @bdraco
|
||||
/homeassistant/components/bluetooth_adapters/ @bdraco
|
||||
@@ -220,6 +223,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/brottsplatskartan/ @gjohansson-ST
|
||||
/homeassistant/components/brunt/ @eavanvalkenburg
|
||||
/tests/components/brunt/ @eavanvalkenburg
|
||||
/homeassistant/components/bryant_evolution/ @danielsmyers
|
||||
/tests/components/bryant_evolution/ @danielsmyers
|
||||
/homeassistant/components/bsblan/ @liudger
|
||||
/tests/components/bsblan/ @liudger
|
||||
/homeassistant/components/bt_smarthub/ @typhoon2099
|
||||
@@ -239,6 +244,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ccm15/ @ocalvo
|
||||
/homeassistant/components/cert_expiry/ @jjlawren
|
||||
/tests/components/cert_expiry/ @jjlawren
|
||||
/homeassistant/components/chacon_dio/ @cnico
|
||||
/tests/components/chacon_dio/ @cnico
|
||||
/homeassistant/components/cisco_ios/ @fbradyirl
|
||||
/homeassistant/components/cisco_mobility_express/ @fbradyirl
|
||||
/homeassistant/components/cisco_webex_teams/ @fbradyirl
|
||||
@@ -342,8 +349,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/dremel_3d_printer/ @tkdrob
|
||||
/homeassistant/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||
/tests/components/drop_connect/ @ChandlerSystems @pfrazer
|
||||
/homeassistant/components/dsmr/ @Robbie1221 @frenck
|
||||
/tests/components/dsmr/ @Robbie1221 @frenck
|
||||
/homeassistant/components/dsmr/ @Robbie1221
|
||||
/tests/components/dsmr/ @Robbie1221
|
||||
/homeassistant/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||
/homeassistant/components/duotecno/ @cereal2nd
|
||||
@@ -360,8 +367,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ecoforest/ @pjanuario
|
||||
/homeassistant/components/econet/ @w1ll1am23
|
||||
/tests/components/econet/ @w1ll1am23
|
||||
/homeassistant/components/ecovacs/ @OverloadUT @mib1185 @edenhaus @Augar
|
||||
/tests/components/ecovacs/ @OverloadUT @mib1185 @edenhaus @Augar
|
||||
/homeassistant/components/ecovacs/ @mib1185 @edenhaus @Augar
|
||||
/tests/components/ecovacs/ @mib1185 @edenhaus @Augar
|
||||
/homeassistant/components/ecowitt/ @pvizeli
|
||||
/tests/components/ecowitt/ @pvizeli
|
||||
/homeassistant/components/efergy/ @tkdrob
|
||||
@@ -371,6 +378,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/electrasmart/ @jafar-atili
|
||||
/homeassistant/components/electric_kiwi/ @mikey0000
|
||||
/tests/components/electric_kiwi/ @mikey0000
|
||||
/homeassistant/components/elevenlabs/ @sorgfresser
|
||||
/tests/components/elevenlabs/ @sorgfresser
|
||||
/homeassistant/components/elgato/ @frenck
|
||||
/tests/components/elgato/ @frenck
|
||||
/homeassistant/components/elkm1/ @gwww @bdraco
|
||||
@@ -382,6 +391,7 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/elvia/ @ludeeus
|
||||
/homeassistant/components/emby/ @mezz64
|
||||
/homeassistant/components/emoncms/ @borpin @alexandrecuer
|
||||
/tests/components/emoncms/ @borpin @alexandrecuer
|
||||
/homeassistant/components/emonitor/ @bdraco
|
||||
/tests/components/emonitor/ @bdraco
|
||||
/homeassistant/components/emulated_hue/ @bdraco @Tho85
|
||||
@@ -398,8 +408,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/enigma2/ @autinerd
|
||||
/homeassistant/components/enocean/ @bdurrer
|
||||
/tests/components/enocean/ @bdurrer
|
||||
/homeassistant/components/enphase_envoy/ @bdraco @cgarwood @dgomes @joostlek @catsmanac
|
||||
/tests/components/enphase_envoy/ @bdraco @cgarwood @dgomes @joostlek @catsmanac
|
||||
/homeassistant/components/enphase_envoy/ @bdraco @cgarwood @joostlek @catsmanac
|
||||
/tests/components/enphase_envoy/ @bdraco @cgarwood @joostlek @catsmanac
|
||||
/homeassistant/components/entur_public_transport/ @hfurubotten
|
||||
/homeassistant/components/environment_canada/ @gwww @michaeldavie
|
||||
/tests/components/environment_canada/ @gwww @michaeldavie
|
||||
@@ -423,6 +433,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/evil_genius_labs/ @balloob
|
||||
/tests/components/evil_genius_labs/ @balloob
|
||||
/homeassistant/components/evohome/ @zxdavb
|
||||
/tests/components/evohome/ @zxdavb
|
||||
/homeassistant/components/ezviz/ @RenierM26 @baqs
|
||||
/tests/components/ezviz/ @RenierM26 @baqs
|
||||
/homeassistant/components/faa_delays/ @ntilley905
|
||||
@@ -431,6 +442,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/fan/ @home-assistant/core
|
||||
/homeassistant/components/fastdotcom/ @rohankapoorcom @erwindouna
|
||||
/tests/components/fastdotcom/ @rohankapoorcom @erwindouna
|
||||
/homeassistant/components/feedreader/ @mib1185
|
||||
/tests/components/feedreader/ @mib1185
|
||||
/homeassistant/components/fibaro/ @rappenze
|
||||
/tests/components/fibaro/ @rappenze
|
||||
/homeassistant/components/file/ @fabaff
|
||||
@@ -486,6 +499,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/frontend/ @home-assistant/frontend
|
||||
/homeassistant/components/frontier_silicon/ @wlcrs
|
||||
/tests/components/frontier_silicon/ @wlcrs
|
||||
/homeassistant/components/fujitsu_fglair/ @crevetor
|
||||
/tests/components/fujitsu_fglair/ @crevetor
|
||||
/homeassistant/components/fully_kiosk/ @cgarwood
|
||||
/tests/components/fully_kiosk/ @cgarwood
|
||||
/homeassistant/components/fyta/ @dontinelli
|
||||
@@ -501,6 +516,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/generic_hygrostat/ @Shulyaka
|
||||
/tests/components/generic_hygrostat/ @Shulyaka
|
||||
/homeassistant/components/geniushub/ @manzanotti
|
||||
/tests/components/geniushub/ @manzanotti
|
||||
/homeassistant/components/geo_json_events/ @exxamalte
|
||||
/tests/components/geo_json_events/ @exxamalte
|
||||
/homeassistant/components/geo_location/ @home-assistant/core
|
||||
@@ -691,6 +707,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ios/ @robbiet480
|
||||
/homeassistant/components/iotawatt/ @gtdiehl @jyavenard
|
||||
/tests/components/iotawatt/ @gtdiehl @jyavenard
|
||||
/homeassistant/components/iotty/ @pburgio
|
||||
/tests/components/iotty/ @pburgio
|
||||
/homeassistant/components/iperf3/ @rohankapoorcom
|
||||
/homeassistant/components/ipma/ @dgomes
|
||||
/tests/components/ipma/ @dgomes
|
||||
@@ -699,10 +717,14 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/iqvia/ @bachya
|
||||
/tests/components/iqvia/ @bachya
|
||||
/homeassistant/components/irish_rail_transport/ @ttroy50
|
||||
/homeassistant/components/iron_os/ @tr4nt0r
|
||||
/tests/components/iron_os/ @tr4nt0r
|
||||
/homeassistant/components/isal/ @bdraco
|
||||
/tests/components/isal/ @bdraco
|
||||
/homeassistant/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||
/tests/components/islamic_prayer_times/ @engrbm87 @cpfair
|
||||
/homeassistant/components/israel_rail/ @shaiu
|
||||
/tests/components/israel_rail/ @shaiu
|
||||
/homeassistant/components/iss/ @DurgNomis-drol
|
||||
/tests/components/iss/ @DurgNomis-drol
|
||||
/homeassistant/components/ista_ecotrend/ @tr4nt0r
|
||||
@@ -737,8 +759,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/kitchen_sink/ @home-assistant/core
|
||||
/homeassistant/components/kmtronic/ @dgomes
|
||||
/tests/components/kmtronic/ @dgomes
|
||||
/homeassistant/components/knocki/ @joostlek @jgatto1
|
||||
/tests/components/knocki/ @joostlek @jgatto1
|
||||
/homeassistant/components/knocki/ @joostlek @jgatto1 @JakeBosh
|
||||
/tests/components/knocki/ @joostlek @jgatto1 @JakeBosh
|
||||
/homeassistant/components/knx/ @Julius2342 @farmio @marvin-w
|
||||
/tests/components/knx/ @Julius2342 @farmio @marvin-w
|
||||
/homeassistant/components/kodi/ @OnFreund
|
||||
@@ -779,10 +801,14 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/lg_netcast/ @Drafteed @splinter98
|
||||
/homeassistant/components/lidarr/ @tkdrob
|
||||
/tests/components/lidarr/ @tkdrob
|
||||
/homeassistant/components/lifx/ @Djelibeybi
|
||||
/tests/components/lifx/ @Djelibeybi
|
||||
/homeassistant/components/light/ @home-assistant/core
|
||||
/tests/components/light/ @home-assistant/core
|
||||
/homeassistant/components/linear_garage_door/ @IceBotYT
|
||||
/tests/components/linear_garage_door/ @IceBotYT
|
||||
/homeassistant/components/linkplay/ @Velleman
|
||||
/tests/components/linkplay/ @Velleman
|
||||
/homeassistant/components/linux_battery/ @fabaff
|
||||
/homeassistant/components/litejet/ @joncar
|
||||
/tests/components/litejet/ @joncar
|
||||
@@ -802,8 +828,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/logbook/ @home-assistant/core
|
||||
/homeassistant/components/logger/ @home-assistant/core
|
||||
/tests/components/logger/ @home-assistant/core
|
||||
/homeassistant/components/logi_circle/ @evanjd
|
||||
/tests/components/logi_circle/ @evanjd
|
||||
/homeassistant/components/london_underground/ @jpbede
|
||||
/tests/components/london_underground/ @jpbede
|
||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||
@@ -823,13 +847,16 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/lutron_caseta/ @swails @bdraco @danaues @eclair4151
|
||||
/homeassistant/components/lyric/ @timmo001
|
||||
/tests/components/lyric/ @timmo001
|
||||
/homeassistant/components/mastodon/ @fabaff
|
||||
/homeassistant/components/madvr/ @iloveicedgreentea
|
||||
/tests/components/madvr/ @iloveicedgreentea
|
||||
/homeassistant/components/mastodon/ @fabaff @andrew-codechimp
|
||||
/tests/components/mastodon/ @fabaff @andrew-codechimp
|
||||
/homeassistant/components/matrix/ @PaarthShah
|
||||
/tests/components/matrix/ @PaarthShah
|
||||
/homeassistant/components/matter/ @home-assistant/matter
|
||||
/tests/components/matter/ @home-assistant/matter
|
||||
/homeassistant/components/mealie/ @joostlek
|
||||
/tests/components/mealie/ @joostlek
|
||||
/homeassistant/components/mealie/ @joostlek @andrew-codechimp
|
||||
/tests/components/mealie/ @joostlek @andrew-codechimp
|
||||
/homeassistant/components/meater/ @Sotolotl @emontnemery
|
||||
/tests/components/meater/ @Sotolotl @emontnemery
|
||||
/homeassistant/components/medcom_ble/ @elafargue
|
||||
@@ -874,8 +901,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/moat/ @bdraco
|
||||
/homeassistant/components/mobile_app/ @home-assistant/core
|
||||
/tests/components/mobile_app/ @home-assistant/core
|
||||
/homeassistant/components/modbus/ @janiversen
|
||||
/tests/components/modbus/ @janiversen
|
||||
/homeassistant/components/modem_callerid/ @tkdrob
|
||||
/tests/components/modem_callerid/ @tkdrob
|
||||
/homeassistant/components/modern_forms/ @wonderslug
|
||||
@@ -945,6 +970,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/nfandroidtv/ @tkdrob
|
||||
/homeassistant/components/nibe_heatpump/ @elupus
|
||||
/tests/components/nibe_heatpump/ @elupus
|
||||
/homeassistant/components/nice_go/ @IceBotYT
|
||||
/tests/components/nice_go/ @IceBotYT
|
||||
/homeassistant/components/nightscout/ @marciogranzotto
|
||||
/tests/components/nightscout/ @marciogranzotto
|
||||
/homeassistant/components/nilu/ @hfurubotten
|
||||
@@ -987,8 +1014,6 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/ollama/ @synesthesiam
|
||||
/tests/components/ollama/ @synesthesiam
|
||||
/homeassistant/components/ombi/ @larssont
|
||||
/homeassistant/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
||||
/tests/components/omnilogic/ @oliver84 @djtimca @gentoosu
|
||||
/homeassistant/components/onboarding/ @home-assistant/core
|
||||
/tests/components/onboarding/ @home-assistant/core
|
||||
/homeassistant/components/oncue/ @bdraco @peterager
|
||||
@@ -1033,8 +1058,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/otbr/ @home-assistant/core
|
||||
/homeassistant/components/ourgroceries/ @OnFreund
|
||||
/tests/components/ourgroceries/ @OnFreund
|
||||
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
|
||||
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117
|
||||
/homeassistant/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
|
||||
/tests/components/overkiz/ @imicknl @vlebourl @tetienne @nyroDev @tronix117 @alexfp14
|
||||
/homeassistant/components/ovo_energy/ @timmo001
|
||||
/tests/components/ovo_energy/ @timmo001
|
||||
/homeassistant/components/p1_monitor/ @klaasnicolaas
|
||||
@@ -1204,6 +1229,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/rtsp_to_webrtc/ @allenporter
|
||||
/homeassistant/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
|
||||
/tests/components/ruckus_unleashed/ @lanrat @ms264556 @gabe565
|
||||
/homeassistant/components/russound_rio/ @noahhusby
|
||||
/tests/components/russound_rio/ @noahhusby
|
||||
/homeassistant/components/ruuvi_gateway/ @akx
|
||||
/tests/components/ruuvi_gateway/ @akx
|
||||
/homeassistant/components/ruuvitag_ble/ @akx
|
||||
@@ -1271,6 +1298,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/sighthound/ @robmarkcole
|
||||
/homeassistant/components/signal_messenger/ @bbernhard
|
||||
/tests/components/signal_messenger/ @bbernhard
|
||||
/homeassistant/components/simplefin/ @scottg489 @jeeftor
|
||||
/tests/components/simplefin/ @scottg489 @jeeftor
|
||||
/homeassistant/components/simplepush/ @engrbm87
|
||||
/tests/components/simplepush/ @engrbm87
|
||||
/homeassistant/components/simplisafe/ @bachya
|
||||
@@ -1300,6 +1329,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/smarty/ @z0mbieprocess
|
||||
/homeassistant/components/smhi/ @gjohansson-ST
|
||||
/tests/components/smhi/ @gjohansson-ST
|
||||
/homeassistant/components/smlight/ @tl-sl
|
||||
/tests/components/smlight/ @tl-sl
|
||||
/homeassistant/components/sms/ @ocalvo
|
||||
/tests/components/sms/ @ocalvo
|
||||
/homeassistant/components/snapcast/ @luar123
|
||||
@@ -1422,6 +1453,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/tellduslive/ @fredrike
|
||||
/homeassistant/components/template/ @PhracturedBlue @tetienne @home-assistant/core
|
||||
/tests/components/template/ @PhracturedBlue @tetienne @home-assistant/core
|
||||
/homeassistant/components/tesla_fleet/ @Bre77
|
||||
/tests/components/tesla_fleet/ @Bre77
|
||||
/homeassistant/components/tesla_wall_connector/ @einarhauks
|
||||
/tests/components/tesla_wall_connector/ @einarhauks
|
||||
/homeassistant/components/teslemetry/ @Bre77
|
||||
|
@@ -12,7 +12,7 @@ ENV \
|
||||
ARG QEMU_CPU
|
||||
|
||||
# Install uv
|
||||
RUN pip3 install uv==0.2.13
|
||||
RUN pip3 install uv==0.2.27
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
|
@@ -4,7 +4,7 @@ coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 90
|
||||
target: auto
|
||||
threshold: 0.09
|
||||
required:
|
||||
target: auto
|
||||
|
@@ -363,15 +363,15 @@ class AuthManager:
|
||||
local_only: bool | None = None,
|
||||
) -> None:
|
||||
"""Update a user."""
|
||||
kwargs: dict[str, Any] = {}
|
||||
|
||||
for attr_name, value in (
|
||||
("name", name),
|
||||
("group_ids", group_ids),
|
||||
("local_only", local_only),
|
||||
):
|
||||
if value is not None:
|
||||
kwargs[attr_name] = value
|
||||
kwargs: dict[str, Any] = {
|
||||
attr_name: value
|
||||
for attr_name, value in (
|
||||
("name", name),
|
||||
("group_ids", group_ids),
|
||||
("local_only", local_only),
|
||||
)
|
||||
if value is not None
|
||||
}
|
||||
await self._store.async_update_user(user, **kwargs)
|
||||
|
||||
if is_active is not None:
|
||||
|
@@ -105,14 +105,18 @@ class AuthStore:
|
||||
"perm_lookup": self._perm_lookup,
|
||||
}
|
||||
|
||||
for attr_name, value in (
|
||||
("is_owner", is_owner),
|
||||
("is_active", is_active),
|
||||
("local_only", local_only),
|
||||
("system_generated", system_generated),
|
||||
):
|
||||
if value is not None:
|
||||
kwargs[attr_name] = value
|
||||
kwargs.update(
|
||||
{
|
||||
attr_name: value
|
||||
for attr_name, value in (
|
||||
("is_owner", is_owner),
|
||||
("is_active", is_active),
|
||||
("local_only", local_only),
|
||||
("system_generated", system_generated),
|
||||
)
|
||||
if value is not None
|
||||
}
|
||||
)
|
||||
|
||||
new_user = models.User(**kwargs)
|
||||
|
||||
|
@@ -18,9 +18,12 @@ from homeassistant.const import (
|
||||
EVENT_THEMES_UPDATED,
|
||||
)
|
||||
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.category_registry import EVENT_CATEGORY_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.floor_registry import EVENT_FLOOR_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.issue_registry import EVENT_REPAIRS_ISSUE_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.label_registry import EVENT_LABEL_REGISTRY_UPDATED
|
||||
from homeassistant.util.event_type import EventType
|
||||
|
||||
# These are events that do not contain any sensitive data
|
||||
@@ -41,4 +44,7 @@ SUBSCRIBE_ALLOWLIST: Final[set[EventType[Any] | str]] = {
|
||||
EVENT_SHOPPING_LIST_UPDATED,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_THEMES_UPDATED,
|
||||
EVENT_LABEL_REGISTRY_UPDATED,
|
||||
EVENT_CATEGORY_REGISTRY_UPDATED,
|
||||
EVENT_FLOOR_REGISTRY_UPDATED,
|
||||
}
|
||||
|
@@ -55,13 +55,6 @@ class InvalidUser(HomeAssistantError):
|
||||
Will not be raised when validating authentication.
|
||||
"""
|
||||
|
||||
|
||||
class InvalidUsername(InvalidUser):
|
||||
"""Raised when invalid username is specified.
|
||||
|
||||
Will not be raised when validating authentication.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args: object,
|
||||
@@ -77,6 +70,13 @@ class InvalidUsername(InvalidUser):
|
||||
)
|
||||
|
||||
|
||||
class InvalidUsername(InvalidUser):
|
||||
"""Raised when invalid username is specified.
|
||||
|
||||
Will not be raised when validating authentication.
|
||||
"""
|
||||
|
||||
|
||||
class Data:
|
||||
"""Hold the user data."""
|
||||
|
||||
@@ -216,7 +216,7 @@ class Data:
|
||||
break
|
||||
|
||||
if index is None:
|
||||
raise InvalidUser
|
||||
raise InvalidUser(translation_key="user_not_found")
|
||||
|
||||
self.users.pop(index)
|
||||
|
||||
@@ -232,7 +232,7 @@ class Data:
|
||||
user["password"] = self.hash_password(new_password, True).decode()
|
||||
break
|
||||
else:
|
||||
raise InvalidUser
|
||||
raise InvalidUser(translation_key="user_not_found")
|
||||
|
||||
@callback
|
||||
def _validate_new_username(self, new_username: str) -> None:
|
||||
@@ -275,7 +275,7 @@ class Data:
|
||||
self._async_check_for_not_normalized_usernames(self._data)
|
||||
break
|
||||
else:
|
||||
raise InvalidUser
|
||||
raise InvalidUser(translation_key="user_not_found")
|
||||
|
||||
async def async_save(self) -> None:
|
||||
"""Save data."""
|
||||
|
@@ -8,6 +8,8 @@ import glob
|
||||
from http.client import HTTPConnection
|
||||
import importlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from ssl import SSLContext
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@@ -143,6 +145,78 @@ _BLOCKING_CALLS: tuple[BlockingCall, ...] = (
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=SSLContext.load_default_certs,
|
||||
object=SSLContext,
|
||||
function="load_default_certs",
|
||||
check_allowed=None,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=SSLContext.load_verify_locations,
|
||||
object=SSLContext,
|
||||
function="load_verify_locations",
|
||||
check_allowed=None,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=SSLContext.load_cert_chain,
|
||||
object=SSLContext,
|
||||
function="load_cert_chain",
|
||||
check_allowed=None,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.open,
|
||||
object=Path,
|
||||
function="open",
|
||||
check_allowed=_check_file_allowed,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.read_text,
|
||||
object=Path,
|
||||
function="read_text",
|
||||
check_allowed=_check_file_allowed,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.read_bytes,
|
||||
object=Path,
|
||||
function="read_bytes",
|
||||
check_allowed=_check_file_allowed,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.write_text,
|
||||
object=Path,
|
||||
function="write_text",
|
||||
check_allowed=_check_file_allowed,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
BlockingCall(
|
||||
original_func=Path.write_bytes,
|
||||
object=Path,
|
||||
function="write_bytes",
|
||||
check_allowed=_check_file_allowed,
|
||||
strict=False,
|
||||
strict_core=False,
|
||||
skip_for_tests=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import contextlib
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
import logging
|
||||
import logging.handlers
|
||||
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
||||
import mimetypes
|
||||
from operator import contains, itemgetter
|
||||
import os
|
||||
@@ -88,7 +88,7 @@ from .helpers import (
|
||||
)
|
||||
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.system_info import async_get_system_info, is_official_image
|
||||
from .helpers.typing import ConfigType
|
||||
from .setup import (
|
||||
# _setup_started is marked as protected to make it clear
|
||||
@@ -104,7 +104,7 @@ from .setup import (
|
||||
from .util.async_ import create_eager_task
|
||||
from .util.hass_dict import HassKey
|
||||
from .util.logging import async_activate_log_queue_handler
|
||||
from .util.package import async_get_user_site, is_virtual_env
|
||||
from .util.package import async_get_user_site, is_docker_env, is_virtual_env
|
||||
|
||||
with contextlib.suppress(ImportError):
|
||||
# Ensure anyio backend is imported to avoid it being imported in the event loop
|
||||
@@ -223,8 +223,10 @@ CRITICAL_INTEGRATIONS = {
|
||||
SETUP_ORDER = (
|
||||
# Load logging and http deps as soon as possible
|
||||
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS),
|
||||
# Setup frontend and recorder
|
||||
("frontend, recorder", {*FRONTEND_INTEGRATIONS, *RECORDER_INTEGRATIONS}),
|
||||
# Setup frontend
|
||||
("frontend", FRONTEND_INTEGRATIONS),
|
||||
# Setup recorder
|
||||
("recorder", RECORDER_INTEGRATIONS),
|
||||
# Start up debuggers. Start these first in case they want to wait.
|
||||
("debugger", DEBUGGER_INTEGRATIONS),
|
||||
)
|
||||
@@ -257,12 +259,12 @@ async def async_setup_hass(
|
||||
) -> core.HomeAssistant | None:
|
||||
"""Set up Home Assistant."""
|
||||
|
||||
def create_hass() -> core.HomeAssistant:
|
||||
async def create_hass() -> core.HomeAssistant:
|
||||
"""Create the hass object and do basic setup."""
|
||||
hass = core.HomeAssistant(runtime_config.config_dir)
|
||||
loader.async_setup(hass)
|
||||
|
||||
async_enable_logging(
|
||||
await async_enable_logging(
|
||||
hass,
|
||||
runtime_config.verbose,
|
||||
runtime_config.log_rotate_days,
|
||||
@@ -287,7 +289,7 @@ async def async_setup_hass(
|
||||
async with hass.timeout.async_timeout(10):
|
||||
await hass.async_stop()
|
||||
|
||||
hass = create_hass()
|
||||
hass = await create_hass()
|
||||
|
||||
if runtime_config.skip_pip or runtime_config.skip_pip_packages:
|
||||
_LOGGER.warning(
|
||||
@@ -326,13 +328,13 @@ async def async_setup_hass(
|
||||
if config_dict is None:
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
hass = await create_hass()
|
||||
|
||||
elif not basic_setup_success:
|
||||
_LOGGER.warning("Unable to set up core integrations. Activating recovery mode")
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
hass = await create_hass()
|
||||
|
||||
elif any(domain not in hass.config.components for domain in CRITICAL_INTEGRATIONS):
|
||||
_LOGGER.warning(
|
||||
@@ -345,7 +347,7 @@ async def async_setup_hass(
|
||||
|
||||
recovery_mode = True
|
||||
await stop_hass(hass)
|
||||
hass = create_hass()
|
||||
hass = await create_hass()
|
||||
|
||||
if old_logging:
|
||||
hass.data[DATA_LOGGING] = old_logging
|
||||
@@ -407,6 +409,10 @@ def _init_blocking_io_modules_in_executor() -> None:
|
||||
# Initialize the mimetypes module to avoid blocking calls
|
||||
# to the filesystem to load the mime.types file.
|
||||
mimetypes.init()
|
||||
# Initialize is_official_image and is_docker_env to avoid blocking calls
|
||||
# to the filesystem.
|
||||
is_official_image()
|
||||
is_docker_env()
|
||||
|
||||
|
||||
async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
|
||||
@@ -523,8 +529,7 @@ async def async_from_config_dict(
|
||||
return hass
|
||||
|
||||
|
||||
@core.callback
|
||||
def async_enable_logging(
|
||||
async def async_enable_logging(
|
||||
hass: core.HomeAssistant,
|
||||
verbose: bool = False,
|
||||
log_rotate_days: int | None = None,
|
||||
@@ -581,10 +586,10 @@ def async_enable_logging(
|
||||
logging.getLogger("aiohttp.access").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
sys.excepthook = lambda *args: logging.getLogger(None).exception(
|
||||
sys.excepthook = lambda *args: logging.getLogger().exception(
|
||||
"Uncaught exception", exc_info=args
|
||||
)
|
||||
threading.excepthook = lambda args: logging.getLogger(None).exception(
|
||||
threading.excepthook = lambda args: logging.getLogger().exception(
|
||||
"Uncaught thread exception",
|
||||
exc_info=( # type: ignore[arg-type]
|
||||
args.exc_type,
|
||||
@@ -607,28 +612,13 @@ def async_enable_logging(
|
||||
if (err_path_exists and os.access(err_log_path, os.W_OK)) or (
|
||||
not err_path_exists and os.access(err_dir, os.W_OK)
|
||||
):
|
||||
err_handler: (
|
||||
logging.handlers.RotatingFileHandler
|
||||
| logging.handlers.TimedRotatingFileHandler
|
||||
err_handler = await hass.async_add_executor_job(
|
||||
_create_log_file, err_log_path, log_rotate_days
|
||||
)
|
||||
if log_rotate_days:
|
||||
err_handler = logging.handlers.TimedRotatingFileHandler(
|
||||
err_log_path, when="midnight", backupCount=log_rotate_days
|
||||
)
|
||||
else:
|
||||
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)
|
||||
|
||||
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
|
||||
err_handler.setFormatter(logging.Formatter(fmt, datefmt=FORMAT_DATETIME))
|
||||
|
||||
logger = logging.getLogger("")
|
||||
logger = logging.getLogger()
|
||||
logger.addHandler(err_handler)
|
||||
logger.setLevel(logging.INFO if verbose else logging.WARNING)
|
||||
|
||||
@@ -640,7 +630,29 @@ def async_enable_logging(
|
||||
async_activate_log_queue_handler(hass)
|
||||
|
||||
|
||||
class _RotatingFileHandlerWithoutShouldRollOver(logging.handlers.RotatingFileHandler):
|
||||
def _create_log_file(
|
||||
err_log_path: str, log_rotate_days: int | None
|
||||
) -> RotatingFileHandler | TimedRotatingFileHandler:
|
||||
"""Create log file and do roll over."""
|
||||
err_handler: RotatingFileHandler | TimedRotatingFileHandler
|
||||
if log_rotate_days:
|
||||
err_handler = TimedRotatingFileHandler(
|
||||
err_log_path, when="midnight", backupCount=log_rotate_days
|
||||
)
|
||||
else:
|
||||
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)
|
||||
|
||||
return err_handler
|
||||
|
||||
|
||||
class _RotatingFileHandlerWithoutShouldRollOver(RotatingFileHandler):
|
||||
"""RotatingFileHandler that does not check if it should roll over on every log."""
|
||||
|
||||
def shouldRollover(self, record: logging.LogRecord) -> bool:
|
||||
@@ -895,7 +907,13 @@ async def _async_resolve_domains_to_setup(
|
||||
await asyncio.gather(*resolve_dependencies_tasks)
|
||||
|
||||
for itg in integrations_to_process:
|
||||
for dep in itg.all_dependencies:
|
||||
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)
|
||||
|
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"domain": "asterisk",
|
||||
"name": "Asterisk",
|
||||
"integrations": ["asterisk_cdr", "asterisk_mbox"]
|
||||
}
|
5
homeassistant/brands/fujitsu.json
Normal file
5
homeassistant/brands/fujitsu.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "fujitsu",
|
||||
"name": "Fujitsu",
|
||||
"integrations": ["fujitsu_anywair", "fujitsu_fglair"]
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "logitech",
|
||||
"name": "Logitech",
|
||||
"integrations": ["harmony", "ue_smart_radio", "squeezebox"]
|
||||
"integrations": ["harmony", "squeezebox"]
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "tesla",
|
||||
"name": "Tesla",
|
||||
"integrations": ["powerwall", "tesla_wall_connector"]
|
||||
"integrations": ["powerwall", "tesla_wall_connector", "tesla_fleet"]
|
||||
}
|
||||
|
@@ -9,5 +9,5 @@
|
||||
},
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["jaraco.abode", "lomond"],
|
||||
"requirements": ["jaraco.abode==5.1.2"]
|
||||
"requirements": ["jaraco.abode==5.2.1"]
|
||||
}
|
||||
|
@@ -81,7 +81,7 @@ class AcerSwitch(SwitchEntity):
|
||||
write_timeout: int,
|
||||
) -> None:
|
||||
"""Init of the Acer projector."""
|
||||
self.ser = serial.Serial(
|
||||
self.serial = serial.Serial(
|
||||
port=serial_port, timeout=timeout, write_timeout=write_timeout
|
||||
)
|
||||
self._serial_port = serial_port
|
||||
@@ -99,16 +99,16 @@ class AcerSwitch(SwitchEntity):
|
||||
# was disconnected during runtime.
|
||||
# This way the projector can be reconnected and will still work
|
||||
try:
|
||||
if not self.ser.is_open:
|
||||
self.ser.open()
|
||||
self.ser.write(msg.encode("utf-8"))
|
||||
if not self.serial.is_open:
|
||||
self.serial.open()
|
||||
self.serial.write(msg.encode("utf-8"))
|
||||
# Size is an experience value there is no real limit.
|
||||
# AFAIK there is no limit and no end character so we will usually
|
||||
# need to wait for timeout
|
||||
ret = self.ser.read_until(size=20).decode("utf-8")
|
||||
ret = self.serial.read_until(size=20).decode("utf-8")
|
||||
except serial.SerialException:
|
||||
_LOGGER.error("Problem communicating with %s", self._serial_port)
|
||||
self.ser.close()
|
||||
self.serial.close()
|
||||
return ret
|
||||
|
||||
def _write_read_format(self, msg: str) -> str:
|
||||
|
@@ -136,7 +136,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
|
||||
# Tuple to hold data needed for notification
|
||||
NotificationItem = namedtuple(
|
||||
NotificationItem = namedtuple( # noqa: PYI024
|
||||
"NotificationItem", "hnotify huser name plc_datatype callback"
|
||||
)
|
||||
|
||||
|
@@ -206,7 +206,8 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set the HVAC Mode and State."""
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
return await self.async_turn_off()
|
||||
await self.async_turn_off()
|
||||
return
|
||||
if hvac_mode == HVACMode.HEAT_COOL and self.preset_mode != ADVANTAGE_AIR_MYAUTO:
|
||||
raise ServiceValidationError("Heat/Cool is not supported in this mode")
|
||||
await self.async_update_ac(
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/aemet",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aemet_opendata"],
|
||||
"requirements": ["AEMET-OpenData==0.5.2"]
|
||||
"requirements": ["AEMET-OpenData==0.5.4"]
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@ async def async_setup_entry(
|
||||
|
||||
platform = async_get_current_platform()
|
||||
for service, method in CAMERA_SERVICES.items():
|
||||
platform.async_register_entity_service(service, {}, method)
|
||||
platform.async_register_entity_service(service, None, method)
|
||||
|
||||
|
||||
class AgentCamera(MjpegCamera):
|
||||
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from airgradient import AirGradientClient
|
||||
from airgradient import AirGradientClient, get_model_name
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, Platform
|
||||
@@ -21,6 +21,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class AirGradientData:
|
||||
type AirGradientConfigEntry = ConfigEntry[AirGradientData]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AirGradientConfigEntry) -> bool:
|
||||
"""Set up Airgradient from a config entry."""
|
||||
|
||||
client = AirGradientClient(
|
||||
@@ -53,7 +54,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, measurement_coordinator.serial_number)},
|
||||
manufacturer="AirGradient",
|
||||
model=measurement_coordinator.data.model,
|
||||
model=get_model_name(measurement_coordinator.data.model),
|
||||
model_id=measurement_coordinator.data.model,
|
||||
serial_number=measurement_coordinator.data.serial_number,
|
||||
sw_version=measurement_coordinator.data.firmware_version,
|
||||
)
|
||||
@@ -68,6 +70,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: AirGradientConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@@ -2,9 +2,13 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from airgradient import AirGradientClient, AirGradientError, ConfigurationControl
|
||||
from airgradient import (
|
||||
AirGradientClient,
|
||||
AirGradientError,
|
||||
AirGradientParseError,
|
||||
ConfigurationControl,
|
||||
)
|
||||
from awesomeversion import AwesomeVersion
|
||||
from mashumaro import MissingField
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
@@ -83,10 +87,10 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self.client = AirGradientClient(user_input[CONF_HOST], session=session)
|
||||
try:
|
||||
current_measures = await self.client.get_current_measures()
|
||||
except AirGradientParseError:
|
||||
return self.async_abort(reason="invalid_version")
|
||||
except AirGradientError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except MissingField:
|
||||
return self.async_abort(reason="invalid_version")
|
||||
else:
|
||||
await self.async_set_unique_id(current_measures.serial_number)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
@@ -19,7 +19,6 @@ if TYPE_CHECKING:
|
||||
class AirGradientCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
||||
"""Class to manage fetching AirGradient data."""
|
||||
|
||||
_update_interval: timedelta
|
||||
config_entry: AirGradientConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: AirGradientClient) -> None:
|
||||
@@ -28,7 +27,7 @@ class AirGradientCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
||||
hass,
|
||||
logger=LOGGER,
|
||||
name=f"AirGradient {client.host}",
|
||||
update_interval=self._update_interval,
|
||||
update_interval=timedelta(minutes=1),
|
||||
)
|
||||
self.client = client
|
||||
assert self.config_entry.unique_id
|
||||
@@ -47,8 +46,6 @@ class AirGradientCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
|
||||
class AirGradientMeasurementCoordinator(AirGradientCoordinator[Measures]):
|
||||
"""Class to manage fetching AirGradient data."""
|
||||
|
||||
_update_interval = timedelta(minutes=1)
|
||||
|
||||
async def _update_data(self) -> Measures:
|
||||
return await self.client.get_current_measures()
|
||||
|
||||
@@ -56,7 +53,5 @@ class AirGradientMeasurementCoordinator(AirGradientCoordinator[Measures]):
|
||||
class AirGradientConfigCoordinator(AirGradientCoordinator[Config]):
|
||||
"""Class to manage fetching AirGradient data."""
|
||||
|
||||
_update_interval = timedelta(minutes=5)
|
||||
|
||||
async def _update_data(self) -> Config:
|
||||
return await self.client.get_config()
|
||||
|
@@ -8,6 +8,34 @@
|
||||
"default": "mdi:lightbulb-on-outline"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"led_bar_brightness": {
|
||||
"default": "mdi:brightness-percent"
|
||||
},
|
||||
"display_brightness": {
|
||||
"default": "mdi:brightness-percent"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"configuration_control": {
|
||||
"default": "mdi:cloud-cog"
|
||||
},
|
||||
"display_temperature_unit": {
|
||||
"default": "mdi:thermometer-lines"
|
||||
},
|
||||
"led_bar_mode": {
|
||||
"default": "mdi:led-strip"
|
||||
},
|
||||
"nox_index_learning_time_offset": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"voc_index_learning_time_offset": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"co2_automatic_baseline_calibration": {
|
||||
"default": "mdi:molecule-co2"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"total_volatile_organic_component_index": {
|
||||
"default": "mdi:molecule"
|
||||
@@ -17,6 +45,32 @@
|
||||
},
|
||||
"pm003_count": {
|
||||
"default": "mdi:blur"
|
||||
},
|
||||
"led_bar_brightness": {
|
||||
"default": "mdi:brightness-percent"
|
||||
},
|
||||
"display_brightness": {
|
||||
"default": "mdi:brightness-percent"
|
||||
},
|
||||
"display_temperature_unit": {
|
||||
"default": "mdi:thermometer-lines"
|
||||
},
|
||||
"led_bar_mode": {
|
||||
"default": "mdi:led-strip"
|
||||
},
|
||||
"nox_index_learning_time_offset": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"voc_index_learning_time_offset": {
|
||||
"default": "mdi:clock-outline"
|
||||
},
|
||||
"co2_automatic_baseline_calibration": {
|
||||
"default": "mdi:molecule-co2"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"post_data_to_airgradient": {
|
||||
"default": "mdi:cogs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airgradient",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airgradient==0.6.0"],
|
||||
"requirements": ["airgradient==0.8.0"],
|
||||
"zeroconf": ["_airgradient._tcp.local."]
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1."
|
||||
},
|
||||
"error": {
|
||||
|
55
homeassistant/components/airgradient/update.py
Normal file
55
homeassistant/components/airgradient/update.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Airgradient Update platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
|
||||
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AirGradientConfigEntry, AirGradientMeasurementCoordinator
|
||||
from .entity import AirGradientEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(hours=1)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: AirGradientConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Airgradient update platform."""
|
||||
|
||||
data = config_entry.runtime_data
|
||||
|
||||
async_add_entities([AirGradientUpdate(data.measurement)], True)
|
||||
|
||||
|
||||
class AirGradientUpdate(AirGradientEntity, UpdateEntity):
|
||||
"""Representation of Airgradient Update."""
|
||||
|
||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||
coordinator: AirGradientMeasurementCoordinator
|
||||
|
||||
def __init__(self, coordinator: AirGradientMeasurementCoordinator) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_unique_id = f"{coordinator.serial_number}-update"
|
||||
|
||||
@cached_property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return True because we need to poll the latest version."""
|
||||
return True
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str:
|
||||
"""Return the installed version of the entity."""
|
||||
return self.coordinator.data.firmware_version
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update the entity."""
|
||||
self._attr_latest_version = (
|
||||
await self.coordinator.client.get_latest_firmware_version(
|
||||
self.coordinator.serial_number
|
||||
)
|
||||
)
|
@@ -156,7 +156,8 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
||||
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
return await self.async_turn_off()
|
||||
await self.async_turn_off()
|
||||
return
|
||||
await self._airtouch.SetCoolingModeForAc(
|
||||
self._ac_number, HA_STATE_TO_AT[hvac_mode]
|
||||
)
|
||||
@@ -262,7 +263,8 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
|
||||
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
return await self.async_turn_off()
|
||||
await self.async_turn_off()
|
||||
return
|
||||
if self.hvac_mode == HVACMode.OFF:
|
||||
await self.async_turn_on()
|
||||
self._unit = self._airtouch.GetGroups()[self._group_number]
|
||||
|
@@ -1,9 +1,11 @@
|
||||
"""Config flow for AirTouch4."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from airtouch4pyapi import AirTouch, AirTouchStatus
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_HOST
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -16,7 +18,9 @@ class AirtouchConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
|
||||
|
@@ -11,7 +11,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.COVER]
|
||||
|
||||
type Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]
|
||||
|
||||
|
@@ -121,6 +121,7 @@ class Airtouch5ClimateEntity(ClimateEntity, Airtouch5Entity):
|
||||
"""Base class for Airtouch5 Climate Entities."""
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_translation_key = DOMAIN
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
134
homeassistant/components/airtouch5/cover.py
Normal file
134
homeassistant/components/airtouch5/cover.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""Representation of the Damper for AirTouch 5 Devices."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient
|
||||
from airtouch5py.packets.zone_control import (
|
||||
ZoneControlZone,
|
||||
ZoneSettingPower,
|
||||
ZoneSettingValue,
|
||||
)
|
||||
from airtouch5py.packets.zone_name import ZoneName
|
||||
from airtouch5py.packets.zone_status import ZoneStatusZone
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import Airtouch5ConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .entity import Airtouch5Entity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: Airtouch5ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Airtouch 5 Cover entities."""
|
||||
client = config_entry.runtime_data
|
||||
|
||||
# Each zone has a cover for its open percentage
|
||||
async_add_entities(
|
||||
Airtouch5ZoneOpenPercentage(
|
||||
client, zone, client.latest_zone_status[zone.zone_number].has_sensor
|
||||
)
|
||||
for zone in client.zones
|
||||
)
|
||||
|
||||
|
||||
class Airtouch5ZoneOpenPercentage(CoverEntity, Airtouch5Entity):
|
||||
"""How open the damper is in each zone."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.DAMPER
|
||||
_attr_translation_key = "damper"
|
||||
|
||||
# Zones with temperature sensors shouldn't be manually controlled.
|
||||
# We allow it but warn the user in the integration documentation.
|
||||
_attr_supported_features = (
|
||||
CoverEntityFeature.SET_POSITION
|
||||
| CoverEntityFeature.OPEN
|
||||
| CoverEntityFeature.CLOSE
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, client: Airtouch5SimpleClient, zone_name: ZoneName, has_sensor: bool
|
||||
) -> None:
|
||||
"""Initialise the Cover Entity."""
|
||||
super().__init__(client)
|
||||
self._zone_name = zone_name
|
||||
|
||||
self._attr_unique_id = f"zone_{zone_name.zone_number}_open_percentage"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"zone_{zone_name.zone_number}")},
|
||||
name=zone_name.zone_name,
|
||||
manufacturer="Polyaire",
|
||||
model="AirTouch 5",
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self, data: dict[int, ZoneStatusZone]) -> None:
|
||||
if self._zone_name.zone_number not in data:
|
||||
return
|
||||
status = data[self._zone_name.zone_number]
|
||||
|
||||
self._attr_current_cover_position = int(status.open_percentage * 100)
|
||||
if status.open_percentage == 0:
|
||||
self._attr_is_closed = True
|
||||
else:
|
||||
self._attr_is_closed = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Add data updated listener after this object has been initialized."""
|
||||
await super().async_added_to_hass()
|
||||
self._client.zone_status_callbacks.append(self._async_update_attrs)
|
||||
self._async_update_attrs(self._client.latest_zone_status)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Remove data updated listener after this object has been initialized."""
|
||||
await super().async_will_remove_from_hass()
|
||||
self._client.zone_status_callbacks.remove(self._async_update_attrs)
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Open the damper."""
|
||||
await self._set_cover_position(100)
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Close damper."""
|
||||
await self._set_cover_position(0)
|
||||
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Update the damper to a specific position."""
|
||||
|
||||
if (position := kwargs.get(ATTR_POSITION)) is None:
|
||||
_LOGGER.debug("Argument `position` is missing in set_cover_position")
|
||||
return
|
||||
await self._set_cover_position(position)
|
||||
|
||||
async def _set_cover_position(self, position_percent: float) -> None:
|
||||
power: ZoneSettingPower
|
||||
|
||||
if position_percent == 0:
|
||||
power = ZoneSettingPower.SET_TO_OFF
|
||||
else:
|
||||
power = ZoneSettingPower.SET_TO_ON
|
||||
|
||||
zcz = ZoneControlZone(
|
||||
self._zone_name.zone_number,
|
||||
ZoneSettingValue.SET_OPEN_PERCENTAGE,
|
||||
power,
|
||||
position_percent / 100.0,
|
||||
)
|
||||
|
||||
packet = self._client.data_packet_factory.zone_control([zcz])
|
||||
await self._client.send_packet(packet)
|
@@ -6,15 +6,12 @@ from airtouch5py.airtouch5_simple_client import Airtouch5SimpleClient
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class Airtouch5Entity(Entity):
|
||||
"""Base class for Airtouch5 entities."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
_attr_translation_key = DOMAIN
|
||||
|
||||
def __init__(self, client: Airtouch5SimpleClient) -> None:
|
||||
"""Initialise the Entity."""
|
||||
|
@@ -27,6 +27,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
"damper": {
|
||||
"name": "[%key:component::cover::entity_component::damper::name%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client,
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
@@ -62,8 +61,6 @@ PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_cloud_api_update_interval(
|
||||
|
@@ -82,33 +82,54 @@ async def async_setup_entry(
|
||||
"""Add Airzone binary sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
binary_sensors: list[AirzoneBinarySensor] = [
|
||||
AirzoneSystemBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_id,
|
||||
system_data,
|
||||
)
|
||||
for system_id, system_data in coordinator.data[AZD_SYSTEMS].items()
|
||||
for description in SYSTEM_BINARY_SENSOR_TYPES
|
||||
if description.key in system_data
|
||||
]
|
||||
added_systems: set[str] = set()
|
||||
added_zones: set[str] = set()
|
||||
|
||||
binary_sensors.extend(
|
||||
AirzoneZoneBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
for description in ZONE_BINARY_SENSOR_TYPES
|
||||
if description.key in zone_data
|
||||
)
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of binary sensors."""
|
||||
|
||||
async_add_entities(binary_sensors)
|
||||
entities: list[AirzoneBinarySensor] = []
|
||||
|
||||
systems_data = coordinator.data.get(AZD_SYSTEMS, {})
|
||||
received_systems = set(systems_data)
|
||||
new_systems = received_systems - added_systems
|
||||
if new_systems:
|
||||
entities.extend(
|
||||
AirzoneSystemBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_id,
|
||||
systems_data.get(system_id),
|
||||
)
|
||||
for system_id in new_systems
|
||||
for description in SYSTEM_BINARY_SENSOR_TYPES
|
||||
if description.key in systems_data.get(system_id)
|
||||
)
|
||||
added_systems.update(new_systems)
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
entities.extend(
|
||||
AirzoneZoneBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_BINARY_SENSOR_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
|
||||
|
||||
class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
|
||||
|
@@ -102,17 +102,31 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
"""Add Airzone climate from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
AirzoneClimate(
|
||||
coordinator,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
)
|
||||
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of climate."""
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
async_add_entities(
|
||||
AirzoneClimate(
|
||||
coordinator,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
|
||||
|
||||
class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
@@ -114,7 +114,7 @@ class AirZoneConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
try:
|
||||
await airzone.get_version()
|
||||
except AirzoneError as err:
|
||||
except (AirzoneError, TimeoutError) as err:
|
||||
raise AbortFlow("cannot_connect") from err
|
||||
|
||||
return await self.async_step_discovered_connection()
|
||||
|
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.7.7"]
|
||||
"requirements": ["aioairzone==0.8.2"]
|
||||
}
|
||||
|
@@ -83,21 +83,34 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
"""Add Airzone select from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for description in ZONE_SELECT_TYPES
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
if description.key in zone_data
|
||||
)
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of select."""
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
async_add_entities(
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_SELECT_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
|
||||
|
||||
class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
|
||||
|
@@ -85,21 +85,37 @@ async def async_setup_entry(
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
sensors: list[AirzoneSensor] = [
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for system_zone_id, zone_data in coordinator.data[AZD_ZONES].items()
|
||||
for description in ZONE_SENSOR_TYPES
|
||||
if description.key in zone_data
|
||||
]
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of sensors."""
|
||||
|
||||
entities: list[AirzoneSensor] = []
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
entities.extend(
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_SENSOR_TYPES
|
||||
if description.key in zones_data.get(system_zone_id)
|
||||
)
|
||||
added_zones.update(new_zones)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
entities: list[AirzoneSensor] = []
|
||||
|
||||
if AZD_HOT_WATER in coordinator.data:
|
||||
sensors.extend(
|
||||
entities.extend(
|
||||
AirzoneHotWaterSensor(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -110,7 +126,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
if AZD_WEBSERVER in coordinator.data:
|
||||
sensors.extend(
|
||||
entities.extend(
|
||||
AirzoneWebServerSensor(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -120,7 +136,10 @@ async def async_setup_entry(
|
||||
if description.key in coordinator.data[AZD_WEBSERVER]
|
||||
)
|
||||
|
||||
async_add_entities(sensors)
|
||||
async_add_entities(entities)
|
||||
|
||||
entry.async_on_unload(coordinator.async_add_listener(_async_entity_listener))
|
||||
_async_entity_listener()
|
||||
|
||||
|
||||
class AirzoneSensor(AirzoneEntity, SensorEntity):
|
||||
|
@@ -61,7 +61,7 @@ async def async_setup_entry(
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone sensors from a config_entry."""
|
||||
"""Add Airzone Water Heater from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
if AZD_HOT_WATER in coordinator.data:
|
||||
async_add_entities([AirzoneWaterHeater(coordinator, entry)])
|
||||
|
@@ -14,6 +14,7 @@ from aioairzone_cloud.const import (
|
||||
AZD_FLOOR_DEMAND,
|
||||
AZD_PROBLEMS,
|
||||
AZD_SYSTEMS,
|
||||
AZD_THERMOSTAT_BATTERY_LOW,
|
||||
AZD_WARNINGS,
|
||||
AZD_ZONES,
|
||||
)
|
||||
@@ -88,6 +89,10 @@ ZONE_BINARY_SENSOR_TYPES: Final[tuple[AirzoneBinarySensorEntityDescription, ...]
|
||||
key=AZD_AQ_ACTIVE,
|
||||
translation_key="air_quality_active",
|
||||
),
|
||||
AirzoneBinarySensorEntityDescription(
|
||||
device_class=BinarySensorDeviceClass.BATTERY,
|
||||
key=AZD_THERMOSTAT_BATTERY_LOW,
|
||||
),
|
||||
AirzoneBinarySensorEntityDescription(
|
||||
device_class=BinarySensorDeviceClass.RUNNING,
|
||||
key=AZD_FLOOR_DEMAND,
|
||||
@@ -156,6 +161,11 @@ class AirzoneBinarySensor(AirzoneEntity, BinarySensorEntity):
|
||||
|
||||
entity_description: AirzoneBinarySensorEntityDescription
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return Airzone Cloud binary sensor availability."""
|
||||
return super().available and self.is_on is not None
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
|
@@ -13,9 +13,12 @@ from aioairzone_cloud.const import (
|
||||
AZD_GROUPS,
|
||||
AZD_HOT_WATERS,
|
||||
AZD_INSTALLATIONS,
|
||||
AZD_MODEL,
|
||||
AZD_NAME,
|
||||
AZD_SYSTEM_ID,
|
||||
AZD_SYSTEMS,
|
||||
AZD_THERMOSTAT_FW,
|
||||
AZD_THERMOSTAT_MODEL,
|
||||
AZD_WEBSERVER,
|
||||
AZD_WEBSERVERS,
|
||||
AZD_ZONES,
|
||||
@@ -69,6 +72,7 @@ class AirzoneAidooEntity(AirzoneEntity):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, aidoo_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=aidoo_data[AZD_MODEL],
|
||||
name=aidoo_data[AZD_NAME],
|
||||
via_device=(DOMAIN, aidoo_data[AZD_WEBSERVER]),
|
||||
)
|
||||
@@ -111,6 +115,7 @@ class AirzoneGroupEntity(AirzoneEntity):
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, group_id)},
|
||||
model="Group",
|
||||
manufacturer=MANUFACTURER,
|
||||
name=group_data[AZD_NAME],
|
||||
)
|
||||
@@ -154,6 +159,7 @@ class AirzoneHotWaterEntity(AirzoneEntity):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, dhw_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model="Hot Water",
|
||||
name=dhw_data[AZD_NAME],
|
||||
via_device=(DOMAIN, dhw_data[AZD_WEBSERVER]),
|
||||
)
|
||||
@@ -195,6 +201,7 @@ class AirzoneInstallationEntity(AirzoneEntity):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, inst_id)},
|
||||
manufacturer=MANUFACTURER,
|
||||
model="Installation",
|
||||
name=inst_data[AZD_NAME],
|
||||
)
|
||||
|
||||
@@ -240,9 +247,11 @@ class AirzoneSystemEntity(AirzoneEntity):
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, system_id)},
|
||||
model=system_data.get(AZD_MODEL),
|
||||
manufacturer=MANUFACTURER,
|
||||
name=system_data[AZD_NAME],
|
||||
via_device=(DOMAIN, system_data[AZD_WEBSERVER]),
|
||||
sw_version=system_data.get(AZD_FIRMWARE),
|
||||
)
|
||||
|
||||
def get_airzone_value(self, key: str) -> Any:
|
||||
@@ -270,6 +279,7 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, ws_id)},
|
||||
identifiers={(DOMAIN, ws_id)},
|
||||
model="WebServer",
|
||||
manufacturer=MANUFACTURER,
|
||||
name=ws_data[AZD_NAME],
|
||||
sw_version=ws_data[AZD_FIRMWARE],
|
||||
@@ -300,9 +310,11 @@ class AirzoneZoneEntity(AirzoneEntity):
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, zone_id)},
|
||||
model=zone_data.get(AZD_THERMOSTAT_MODEL),
|
||||
manufacturer=MANUFACTURER,
|
||||
name=zone_data[AZD_NAME],
|
||||
via_device=(DOMAIN, self.system_id),
|
||||
sw_version=zone_data.get(AZD_THERMOSTAT_FW),
|
||||
)
|
||||
|
||||
def get_airzone_value(self, key: str) -> Any:
|
||||
|
15
homeassistant/components/airzone_cloud/icons.json
Normal file
15
homeassistant/components/airzone_cloud/icons.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"cpu_usage": {
|
||||
"default": "mdi:cpu-32-bit"
|
||||
},
|
||||
"free_memory": {
|
||||
"default": "mdi:memory"
|
||||
},
|
||||
"thermostat_coverage": {
|
||||
"default": "mdi:signal"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.5.3"]
|
||||
"requirements": ["aioairzone-cloud==0.6.2"]
|
||||
}
|
||||
|
@@ -10,8 +10,12 @@ from aioairzone_cloud.const import (
|
||||
AZD_AQ_PM_1,
|
||||
AZD_AQ_PM_2P5,
|
||||
AZD_AQ_PM_10,
|
||||
AZD_CPU_USAGE,
|
||||
AZD_HUMIDITY,
|
||||
AZD_MEMORY_FREE,
|
||||
AZD_TEMP,
|
||||
AZD_THERMOSTAT_BATTERY,
|
||||
AZD_THERMOSTAT_COVERAGE,
|
||||
AZD_WEBSERVERS,
|
||||
AZD_WIFI_RSSI,
|
||||
AZD_ZONES,
|
||||
@@ -28,6 +32,7 @@ from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfInformation,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -52,6 +57,22 @@ AIDOO_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
)
|
||||
|
||||
WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
|
||||
SensorEntityDescription(
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_CPU_USAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="cpu_usage",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
key=AZD_MEMORY_FREE,
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="free_memory",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -98,6 +119,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_COVERAGE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
translation_key="thermostat_coverage",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -154,6 +189,11 @@ async def async_setup_entry(
|
||||
class AirzoneSensor(AirzoneEntity, SensorEntity):
|
||||
"""Define an Airzone Cloud sensor."""
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return Airzone Cloud sensor availability."""
|
||||
return super().available and self.native_value is not None
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
|
@@ -37,6 +37,17 @@
|
||||
"auto": "Auto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"cpu_usage": {
|
||||
"name": "CPU usage"
|
||||
},
|
||||
"free_memory": {
|
||||
"name": "Free memory"
|
||||
},
|
||||
"thermostat_coverage": {
|
||||
"name": "Signal percentage"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,94 +1,38 @@
|
||||
"""The Aladdin Connect Genie integration."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
OAuth2Session,
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AladdinConnectCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
|
||||
|
||||
type AladdinConnectConfigEntry = ConfigEntry[AladdinConnectCoordinator]
|
||||
DOMAIN = "aladdin_connect"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Aladdin Connect Genie from a config entry."""
|
||||
implementation = await async_get_config_entry_implementation(hass, entry)
|
||||
|
||||
session = OAuth2Session(hass, entry, implementation)
|
||||
auth = AsyncConfigEntryAuth(async_get_clientsession(hass), session)
|
||||
coordinator = AladdinConnectCoordinator(hass, AladdinConnectClient(auth))
|
||||
|
||||
await coordinator.async_setup()
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async_remove_stale_devices(hass, entry)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old config."""
|
||||
if config_entry.version < 2:
|
||||
config_entry.async_start_reauth(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry,
|
||||
version=2,
|
||||
minor_version=1,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def async_remove_stale_devices(
|
||||
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
|
||||
) -> None:
|
||||
"""Remove stale devices from device registry."""
|
||||
device_registry = dr.async_get(hass)
|
||||
device_entries = dr.async_entries_for_config_entry(
|
||||
device_registry, config_entry.entry_id
|
||||
async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
||||
"""Set up Aladdin Connect from a config entry."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"entries": "/config/integrations/integration/aladdin_connect",
|
||||
},
|
||||
)
|
||||
all_device_ids = {door.unique_id for door in config_entry.runtime_data.doors}
|
||||
|
||||
for device_entry in device_entries:
|
||||
device_id: str | None = None
|
||||
return True
|
||||
|
||||
for identifier in device_entry.identifiers:
|
||||
if identifier[0] == DOMAIN:
|
||||
device_id = identifier[1]
|
||||
break
|
||||
|
||||
if device_id is None or device_id not in all_device_ids:
|
||||
# If device_id is None an invalid device entry was found for this config entry.
|
||||
# If the device_id is not in existing device ids it's a stale device entry.
|
||||
# Remove config entry from this device entry in either case.
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, remove_config_entry_id=config_entry.entry_id
|
||||
)
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if all(
|
||||
config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
||||
if config_entry.entry_id != entry.entry_id
|
||||
):
|
||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||
|
||||
return True
|
||||
|
@@ -1,33 +0,0 @@
|
||||
"""API for Aladdin Connect Genie bound to Home Assistant OAuth."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
# from genie_partner_sdk.auth import Auth
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
|
||||
API_URL = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1"
|
||||
API_KEY = "k6QaiQmcTm2zfaNns5L1Z8duBtJmhDOW8JawlCC3"
|
||||
|
||||
|
||||
class AsyncConfigEntryAuth(Auth): # type: ignore[misc]
|
||||
"""Provide Aladdin Connect Genie authentication tied to an OAuth2 based config entry."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
websession: ClientSession,
|
||||
oauth_session: OAuth2Session,
|
||||
) -> None:
|
||||
"""Initialize Aladdin Connect Genie auth."""
|
||||
super().__init__(
|
||||
websession, API_URL, oauth_session.token["access_token"], API_KEY
|
||||
)
|
||||
self._oauth_session = oauth_session
|
||||
|
||||
async def async_get_access_token(self) -> str:
|
||||
"""Return a valid access token."""
|
||||
await self._oauth_session.async_ensure_token_valid()
|
||||
|
||||
return cast(str, self._oauth_session.token["access_token"])
|
@@ -1,70 +1,11 @@
|
||||
"""Config flow for Aladdin Connect Genie."""
|
||||
"""Config flow for Aladdin Connect integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
|
||||
import jwt
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class AladdinConnectOAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Config flow to handle Aladdin Connect Genie OAuth2 authentication."""
|
||||
class AladdinConnectConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Aladdin Connect."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 1
|
||||
|
||||
reauth_entry: ConfigEntry | None = None
|
||||
|
||||
async def async_step_reauth(
|
||||
self, user_input: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Perform reauth upon API auth error or upgrade from v1 to v2."""
|
||||
self.reauth_entry = self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
)
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: Mapping[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Create an oauth config entry or update existing entry for reauth."""
|
||||
token_payload = jwt.decode(
|
||||
data[CONF_TOKEN][CONF_ACCESS_TOKEN], options={"verify_signature": False}
|
||||
)
|
||||
if not self.reauth_entry:
|
||||
await self.async_set_unique_id(token_payload["sub"])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=token_payload["username"],
|
||||
data=data,
|
||||
)
|
||||
|
||||
if self.reauth_entry.unique_id == token_payload["username"]:
|
||||
return self.async_update_reload_and_abort(
|
||||
self.reauth_entry,
|
||||
data=data,
|
||||
unique_id=token_payload["sub"],
|
||||
)
|
||||
if self.reauth_entry.unique_id == token_payload["sub"]:
|
||||
return self.async_update_reload_and_abort(self.reauth_entry, data=data)
|
||||
|
||||
return self.async_abort(reason="wrong_account")
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
VERSION = 1
|
||||
|
@@ -1,6 +0,0 @@
|
||||
"""Constants for the Aladdin Connect Genie integration."""
|
||||
|
||||
DOMAIN = "aladdin_connect"
|
||||
|
||||
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.net/login.html"
|
||||
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
|
@@ -1,38 +0,0 @@
|
||||
"""Define an object to coordinate fetching Aladdin Connect data."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AladdinConnectCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Aladdin Connect Data Update Coordinator."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, acc: AladdinConnectClient) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
logger=_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=15),
|
||||
)
|
||||
self.acc = acc
|
||||
self.doors: list[GarageDoor] = []
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Fetch initial data."""
|
||||
self.doors = await self.acc.get_doors()
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data from API endpoint."""
|
||||
for door in self.doors:
|
||||
await self.acc.update_door(door.device_id, door.door_number)
|
@@ -1,84 +0,0 @@
|
||||
"""Cover Entity for Genie Garage Door."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from typing import Any
|
||||
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.components.cover import (
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
|
||||
from .entity import AladdinConnectEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: AladdinConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Aladdin Connect platform."""
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(AladdinDevice(coordinator, door) for door in coordinator.doors)
|
||||
|
||||
|
||||
class AladdinDevice(AladdinConnectEntity, CoverEntity):
|
||||
"""Representation of Aladdin Connect cover."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.GARAGE
|
||||
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
|
||||
) -> None:
|
||||
"""Initialize the Aladdin Connect cover."""
|
||||
super().__init__(coordinator, device)
|
||||
self._attr_unique_id = device.unique_id
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue open command to cover."""
|
||||
await self.coordinator.acc.open_door(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue close command to cover."""
|
||||
await self.coordinator.acc.close_door(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Update is closed attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "closed")
|
||||
|
||||
@property
|
||||
def is_closing(self) -> bool | None:
|
||||
"""Update is closing attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "closing")
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool | None:
|
||||
"""Update is opening attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "opening")
|
@@ -1,27 +0,0 @@
|
||||
"""Defines a base Aladdin Connect entity."""
|
||||
# mypy: ignore-errors
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AladdinConnectCoordinator
|
||||
|
||||
|
||||
class AladdinConnectEntity(CoordinatorEntity[AladdinConnectCoordinator]):
|
||||
"""Defines a base Aladdin Connect entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._device = device
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.unique_id)},
|
||||
name=device.name,
|
||||
manufacturer="Overhead Door",
|
||||
)
|
@@ -1,11 +1,9 @@
|
||||
{
|
||||
"domain": "aladdin_connect",
|
||||
"name": "Aladdin Connect",
|
||||
"codeowners": ["@swcloudgenie"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials"],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["genie-partner-sdk==1.0.2"]
|
||||
"requirements": []
|
||||
}
|
||||
|
@@ -1,80 +0,0 @@
|
||||
"""Support for Aladdin Connect Garage Door sensors."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
|
||||
from .entity import AladdinConnectEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AccSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes AladdinConnect sensor entity."""
|
||||
|
||||
value_fn: Callable[[AladdinConnectClient, str, int], float | None]
|
||||
|
||||
|
||||
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
AccSensorEntityDescription(
|
||||
key="battery_level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=AladdinConnectClient.get_battery_status,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AladdinConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Aladdin Connect sensor devices."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
AladdinConnectSensor(coordinator, door, description)
|
||||
for description in SENSORS
|
||||
for door in coordinator.doors
|
||||
)
|
||||
|
||||
|
||||
class AladdinConnectSensor(AladdinConnectEntity, SensorEntity):
|
||||
"""A sensor implementation for Aladdin Connect devices."""
|
||||
|
||||
entity_description: AccSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AladdinConnectCoordinator,
|
||||
device: GarageDoor,
|
||||
description: AccSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a sensor for an Aladdin Connect device."""
|
||||
super().__init__(coordinator, device)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{device.unique_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(
|
||||
self.coordinator.acc, self._device.device_id, self._device.door_number
|
||||
)
|
@@ -1,29 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "Aladdin Connect needs to re-authenticate your account"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
|
||||
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"title": "The Aladdin Connect integration has been removed",
|
||||
"description": "The Aladdin Connect integration has been removed from Home Assistant.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing Aladdin Connect integration entries]({entries})."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -124,9 +124,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
if not entities:
|
||||
return False
|
||||
|
||||
component.async_register_entity_service(SERVICE_TURN_OFF, {}, "async_turn_off")
|
||||
component.async_register_entity_service(SERVICE_TURN_ON, {}, "async_turn_on")
|
||||
component.async_register_entity_service(SERVICE_TOGGLE, {}, "async_toggle")
|
||||
component.async_register_entity_service(SERVICE_TURN_OFF, None, "async_turn_off")
|
||||
component.async_register_entity_service(SERVICE_TURN_ON, None, "async_turn_on")
|
||||
component.async_register_entity_service(SERVICE_TOGGLE, None, "async_toggle")
|
||||
|
||||
await component.async_add_entities(entities)
|
||||
|
||||
@@ -162,16 +162,8 @@ class Alert(Entity):
|
||||
self._data = data
|
||||
|
||||
self._message_template = message_template
|
||||
if self._message_template is not None:
|
||||
self._message_template.hass = hass
|
||||
|
||||
self._done_message_template = done_message_template
|
||||
if self._done_message_template is not None:
|
||||
self._done_message_template.hass = hass
|
||||
|
||||
self._title_template = title_template
|
||||
if self._title_template is not None:
|
||||
self._title_template.hass = hass
|
||||
|
||||
self._notifiers = notifiers
|
||||
self._can_ack = can_ack
|
||||
|
@@ -2,11 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Generator
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import Generator
|
||||
|
||||
from homeassistant.components import (
|
||||
button,
|
||||
climate,
|
||||
@@ -19,6 +18,7 @@ from homeassistant.components import (
|
||||
light,
|
||||
media_player,
|
||||
number,
|
||||
remote,
|
||||
timer,
|
||||
vacuum,
|
||||
valve,
|
||||
@@ -439,6 +439,8 @@ class AlexaPowerController(AlexaCapability):
|
||||
is_on = self.entity.state == fan.STATE_ON
|
||||
elif self.entity.domain == humidifier.DOMAIN:
|
||||
is_on = self.entity.state == humidifier.STATE_ON
|
||||
elif self.entity.domain == remote.DOMAIN:
|
||||
is_on = self.entity.state not in (STATE_OFF, STATE_UNKNOWN)
|
||||
elif self.entity.domain == vacuum.DOMAIN:
|
||||
is_on = self.entity.state == vacuum.STATE_CLEANING
|
||||
elif self.entity.domain == timer.DOMAIN:
|
||||
@@ -1436,6 +1438,12 @@ class AlexaModeController(AlexaCapability):
|
||||
if mode in modes:
|
||||
return f"{humidifier.ATTR_MODE}.{mode}"
|
||||
|
||||
# Remote Activity
|
||||
if self.instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
|
||||
activity = self.entity.attributes.get(remote.ATTR_CURRENT_ACTIVITY, None)
|
||||
if activity in self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST, []):
|
||||
return f"{remote.ATTR_ACTIVITY}.{activity}"
|
||||
|
||||
# Water heater operation mode
|
||||
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
||||
operation_mode = self.entity.attributes.get(
|
||||
@@ -1550,6 +1558,24 @@ class AlexaModeController(AlexaCapability):
|
||||
)
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Remote Resource
|
||||
if self.instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
|
||||
# Use the mode controller for a remote because the input controller
|
||||
# only allows a preset of names as an input.
|
||||
self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
|
||||
activities = self.entity.attributes.get(remote.ATTR_ACTIVITY_LIST) or []
|
||||
for activity in activities:
|
||||
self._resource.add_mode(
|
||||
f"{remote.ATTR_ACTIVITY}.{activity}", [activity]
|
||||
)
|
||||
# Remotes with a single activity completely break Alexa discovery, add a
|
||||
# fake activity to the mode controller (see issue #53832).
|
||||
if len(activities) == 1:
|
||||
self._resource.add_mode(
|
||||
f"{remote.ATTR_ACTIVITY}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
|
||||
)
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Cover Position Resources
|
||||
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
self._resource = AlexaModeResource(
|
||||
|
@@ -88,7 +88,7 @@ API_THERMOSTAT_MODES_CUSTOM = {
|
||||
API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"}
|
||||
|
||||
# AlexaModeController does not like a single mode for the fan preset or humidifier mode,
|
||||
# we add PRESET_MODE_NA if a fan / humidifier has only one preset_mode
|
||||
# we add PRESET_MODE_NA if a fan / humidifier / remote has only one preset_mode
|
||||
PRESET_MODE_NA = "-"
|
||||
|
||||
STORAGE_ACCESS_TOKEN = "access_token"
|
||||
|
@@ -2,12 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from collections.abc import Generator, Iterable
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from typing_extensions import Generator
|
||||
|
||||
from homeassistant.components import (
|
||||
alarm_control_panel,
|
||||
alert,
|
||||
@@ -29,6 +27,7 @@ from homeassistant.components import (
|
||||
lock,
|
||||
media_player,
|
||||
number,
|
||||
remote,
|
||||
scene,
|
||||
script,
|
||||
sensor,
|
||||
@@ -198,6 +197,10 @@ class DisplayCategory:
|
||||
# Indicates a device that prints.
|
||||
PRINTER = "PRINTER"
|
||||
|
||||
# Indicates a decive that support stateless events,
|
||||
# such as remote switches and smart buttons.
|
||||
REMOTE = "REMOTE"
|
||||
|
||||
# Indicates a network router.
|
||||
ROUTER = "ROUTER"
|
||||
|
||||
@@ -647,6 +650,24 @@ class FanCapabilities(AlexaEntity):
|
||||
yield Alexa(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(remote.DOMAIN)
|
||||
class RemoteCapabilities(AlexaEntity):
|
||||
"""Class to represent Remote capabilities."""
|
||||
|
||||
def default_display_categories(self) -> list[str]:
|
||||
"""Return the display categories for this entity."""
|
||||
return [DisplayCategory.REMOTE]
|
||||
|
||||
def interfaces(self) -> Generator[AlexaCapability]:
|
||||
"""Yield the supported interfaces."""
|
||||
yield AlexaPowerController(self.entity)
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}"
|
||||
)
|
||||
yield AlexaEndpointHealth(self.hass, self.entity)
|
||||
yield Alexa(self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(humidifier.DOMAIN)
|
||||
class HumidifierCapabilities(AlexaEntity):
|
||||
"""Class to represent Humidifier capabilities."""
|
||||
|
@@ -52,7 +52,6 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
|
||||
"""Initialize Alexa view."""
|
||||
super().__init__()
|
||||
self.flash_briefings = flash_briefings
|
||||
template.attach(hass, self.flash_briefings)
|
||||
|
||||
@callback
|
||||
def get(
|
||||
|
@@ -21,6 +21,7 @@ from homeassistant.components import (
|
||||
light,
|
||||
media_player,
|
||||
number,
|
||||
remote,
|
||||
timer,
|
||||
vacuum,
|
||||
valve,
|
||||
@@ -185,6 +186,8 @@ async def async_api_turn_on(
|
||||
service = fan.SERVICE_TURN_ON
|
||||
elif domain == humidifier.DOMAIN:
|
||||
service = humidifier.SERVICE_TURN_ON
|
||||
elif domain == remote.DOMAIN:
|
||||
service = remote.SERVICE_TURN_ON
|
||||
elif domain == vacuum.DOMAIN:
|
||||
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if (
|
||||
@@ -234,6 +237,8 @@ async def async_api_turn_off(
|
||||
service = climate.SERVICE_TURN_OFF
|
||||
elif domain == fan.DOMAIN:
|
||||
service = fan.SERVICE_TURN_OFF
|
||||
elif domain == remote.DOMAIN:
|
||||
service = remote.SERVICE_TURN_OFF
|
||||
elif domain == humidifier.DOMAIN:
|
||||
service = humidifier.SERVICE_TURN_OFF
|
||||
elif domain == vacuum.DOMAIN:
|
||||
@@ -1200,6 +1205,17 @@ async def async_api_set_mode(
|
||||
msg = f"Entity '{entity.entity_id}' does not support Mode '{mode}'"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
|
||||
# Remote Activity
|
||||
elif instance == f"{remote.DOMAIN}.{remote.ATTR_ACTIVITY}":
|
||||
activity = mode.split(".")[1]
|
||||
activities: list[str] | None = entity.attributes.get(remote.ATTR_ACTIVITY_LIST)
|
||||
if activity != PRESET_MODE_NA and activities and activity in activities:
|
||||
service = remote.SERVICE_TURN_ON
|
||||
data[remote.ATTR_ACTIVITY] = activity
|
||||
else:
|
||||
msg = f"Entity '{entity.entity_id}' does not support Mode '{mode}'"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
|
||||
# Water heater operation mode
|
||||
elif instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
|
||||
operation_mode = mode.split(".")[1]
|
||||
@@ -1497,7 +1513,7 @@ async def async_api_adjust_range(
|
||||
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
|
||||
service = SERVICE_SET_COVER_POSITION
|
||||
if not (current := entity.attributes.get(cover.ATTR_POSITION)):
|
||||
if not (current := entity.attributes.get(cover.ATTR_CURRENT_POSITION)):
|
||||
msg = f"Unable to determine {entity.entity_id} current position"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
position = response_value = min(100, max(0, range_delta + current))
|
||||
|
@@ -5,5 +5,6 @@
|
||||
"codeowners": ["@home-assistant/cloud", "@ochlocracy", "@jbouwh"],
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/alexa",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push"
|
||||
}
|
||||
|
@@ -283,7 +283,7 @@ class AlexaPresetResource(AlexaCapabilityResource):
|
||||
"""Implements Alexa PresetResources.
|
||||
|
||||
Use presetResources with RangeController to provide a set of
|
||||
friendlyNamesfor each RangeController preset.
|
||||
friendlyNames for each RangeController preset.
|
||||
|
||||
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
|
||||
"""
|
||||
|
@@ -194,7 +194,7 @@ async def async_handle_message(
|
||||
|
||||
try:
|
||||
if not enabled:
|
||||
raise AlexaBridgeUnreachableError(
|
||||
raise AlexaBridgeUnreachableError( # noqa: TRY301
|
||||
"Alexa API not enabled in Home Assistant configuration"
|
||||
)
|
||||
|
||||
|
@@ -8,128 +8,23 @@ CONF_REGION: Final = "region_name"
|
||||
CONF_ACCESS_KEY_ID: Final = "aws_access_key_id"
|
||||
CONF_SECRET_ACCESS_KEY: Final = "aws_secret_access_key"
|
||||
|
||||
DEFAULT_REGION: Final = "us-east-1"
|
||||
SUPPORTED_REGIONS: Final[list[str]] = [
|
||||
"us-east-1",
|
||||
"us-east-2",
|
||||
"us-west-1",
|
||||
"us-west-2",
|
||||
"ca-central-1",
|
||||
"eu-west-1",
|
||||
"eu-central-1",
|
||||
"eu-west-2",
|
||||
"eu-west-3",
|
||||
"ap-southeast-1",
|
||||
"ap-southeast-2",
|
||||
"ap-northeast-2",
|
||||
"ap-northeast-1",
|
||||
"ap-south-1",
|
||||
"sa-east-1",
|
||||
]
|
||||
|
||||
CONF_ENGINE: Final = "engine"
|
||||
CONF_VOICE: Final = "voice"
|
||||
CONF_OUTPUT_FORMAT: Final = "output_format"
|
||||
CONF_SAMPLE_RATE: Final = "sample_rate"
|
||||
CONF_TEXT_TYPE: Final = "text_type"
|
||||
|
||||
SUPPORTED_VOICES: Final[list[str]] = [
|
||||
"Aditi", # Hindi
|
||||
"Amy", # English (British)
|
||||
"Aria", # English (New Zealand), Neural
|
||||
"Arlet", # Catalan, Neural
|
||||
"Arthur", # English, Neural
|
||||
"Astrid", # Swedish
|
||||
"Ayanda", # English (South African), Neural
|
||||
"Bianca", # Italian
|
||||
"Brian", # English (British)
|
||||
"Camila", # Portuguese, Brazilian
|
||||
"Carla", # Italian
|
||||
"Carmen", # Romanian
|
||||
"Celine", # French
|
||||
"Chantal", # French Canadian
|
||||
"Conchita", # Spanish (European)
|
||||
"Cristiano", # Portuguese (European)
|
||||
"Daniel", # German, Neural
|
||||
"Dora", # Icelandic
|
||||
"Elin", # Swedish, Neural
|
||||
"Emma", # English
|
||||
"Enrique", # Spanish (European)
|
||||
"Ewa", # Polish
|
||||
"Filiz", # Turkish
|
||||
"Gabrielle", # French (Canadian)
|
||||
"Geraint", # English Welsh
|
||||
"Giorgio", # Italian
|
||||
"Gwyneth", # Welsh
|
||||
"Hala", # Arabic (Gulf), Neural
|
||||
"Hannah", # German (Austrian), Neural
|
||||
"Hans", # German
|
||||
"Hiujin", # Chinese (Cantonese), Neural
|
||||
"Ida", # Norwegian, Neural
|
||||
"Ines", # Portuguese, European # codespell:ignore ines
|
||||
"Ivy", # English
|
||||
"Jacek", # Polish
|
||||
"Jan", # Polish
|
||||
"Joanna", # English
|
||||
"Joey", # English
|
||||
"Justin", # English
|
||||
"Kajal", # English (Indian)/Hindi (Bilingual ), Neural
|
||||
"Karl", # Icelandic
|
||||
"Kendra", # English
|
||||
"Kevin", # English, Neural
|
||||
"Kimberly", # English
|
||||
"Laura", # Dutch, Neural
|
||||
"Lea", # French
|
||||
"Liam", # Canadian French, Neural
|
||||
"Liv", # Norwegian
|
||||
"Lotte", # Dutch
|
||||
"Lucia", # Spanish European
|
||||
"Lupe", # Spanish US
|
||||
"Mads", # Danish
|
||||
"Maja", # Polish
|
||||
"Marlene", # German
|
||||
"Mathieu", # French
|
||||
"Matthew", # English
|
||||
"Maxim", # Russian
|
||||
"Mia", # Spanish Mexican
|
||||
"Miguel", # Spanish US
|
||||
"Mizuki", # Japanese
|
||||
"Naja", # Danish
|
||||
"Nicole", # English Australian
|
||||
"Ola", # Polish, Neural
|
||||
"Olivia", # Female, Australian, Neural
|
||||
"Penelope", # Spanish US
|
||||
"Pedro", # Spanish US, Neural
|
||||
"Raveena", # English, Indian
|
||||
"Ricardo", # Portuguese (Brazilian)
|
||||
"Ruben", # Dutch
|
||||
"Russell", # English (Australian)
|
||||
"Ruth", # English, Neural
|
||||
"Salli", # English
|
||||
"Seoyeon", # Korean
|
||||
"Stephen", # English, Neural
|
||||
"Suvi", # Finnish
|
||||
"Takumi", # Japanese
|
||||
"Tatyana", # Russian
|
||||
"Vicki", # German
|
||||
"Vitoria", # Portuguese, Brazilian
|
||||
"Zeina", # Arabic
|
||||
"Zhiyu", # Chinese
|
||||
]
|
||||
SUPPORTED_OUTPUT_FORMATS: Final[set[str]] = {"mp3", "ogg_vorbis", "pcm"}
|
||||
|
||||
SUPPORTED_OUTPUT_FORMATS: Final[list[str]] = ["mp3", "ogg_vorbis", "pcm"]
|
||||
SUPPORTED_SAMPLE_RATES: Final[set[str]] = {"8000", "16000", "22050", "24000"}
|
||||
|
||||
SUPPORTED_ENGINES: Final[list[str]] = ["neural", "standard"]
|
||||
|
||||
SUPPORTED_SAMPLE_RATES: Final[list[str]] = ["8000", "16000", "22050", "24000"]
|
||||
|
||||
SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, list[str]]] = {
|
||||
"mp3": ["8000", "16000", "22050", "24000"],
|
||||
"ogg_vorbis": ["8000", "16000", "22050"],
|
||||
"pcm": ["8000", "16000"],
|
||||
SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, set[str]]] = {
|
||||
"mp3": {"8000", "16000", "22050", "24000"},
|
||||
"ogg_vorbis": {"8000", "16000", "22050"},
|
||||
"pcm": {"8000", "16000"},
|
||||
}
|
||||
|
||||
SUPPORTED_TEXT_TYPES: Final[list[str]] = ["text", "ssml"]
|
||||
SUPPORTED_TEXT_TYPES: Final[set[str]] = {"text", "ssml"}
|
||||
|
||||
CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = {
|
||||
"audio/mpeg": "mp3",
|
||||
@@ -137,6 +32,8 @@ CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = {
|
||||
"audio/pcm": "pcm",
|
||||
}
|
||||
|
||||
DEFAULT_REGION: Final = "us-east-1"
|
||||
|
||||
DEFAULT_ENGINE: Final = "standard"
|
||||
DEFAULT_VOICE: Final = "Joanna"
|
||||
DEFAULT_OUTPUT_FORMAT: Final = "mp3"
|
||||
|
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/amazon_polly",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["boto3", "botocore", "s3transfer"],
|
||||
"requirements": ["boto3==1.34.51"]
|
||||
"requirements": ["boto3==1.34.131"]
|
||||
}
|
||||
|
@@ -16,6 +16,11 @@ from homeassistant.components.tts import (
|
||||
)
|
||||
from homeassistant.const import ATTR_CREDENTIALS, CONF_PROFILE_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.generated.amazon_polly import (
|
||||
SUPPORTED_ENGINES,
|
||||
SUPPORTED_REGIONS,
|
||||
SUPPORTED_VOICES,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
@@ -38,13 +43,10 @@ from .const import (
|
||||
DEFAULT_SAMPLE_RATES,
|
||||
DEFAULT_TEXT_TYPE,
|
||||
DEFAULT_VOICE,
|
||||
SUPPORTED_ENGINES,
|
||||
SUPPORTED_OUTPUT_FORMATS,
|
||||
SUPPORTED_REGIONS,
|
||||
SUPPORTED_SAMPLE_RATES,
|
||||
SUPPORTED_SAMPLE_RATES_MAP,
|
||||
SUPPORTED_TEXT_TYPES,
|
||||
SUPPORTED_VOICES,
|
||||
)
|
||||
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
@@ -71,6 +71,18 @@ class AmberPriceSpikeBinarySensor(AmberPriceGridSensor):
|
||||
}
|
||||
|
||||
|
||||
class AmberDemandWindowBinarySensor(AmberPriceGridSensor):
|
||||
"""Sensor to show whether demand window is active."""
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if the binary sensor is on."""
|
||||
grid = self.coordinator.data["grid"]
|
||||
if "demand_window" in grid:
|
||||
return grid["demand_window"] # type: ignore[no-any-return]
|
||||
return None
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
@@ -83,6 +95,14 @@ async def async_setup_entry(
|
||||
key="price_spike",
|
||||
name=f"{entry.title} - Price Spike",
|
||||
)
|
||||
async_add_entities(
|
||||
[AmberPriceSpikeBinarySensor(coordinator, price_spike_description)]
|
||||
demand_window_description = BinarySensorEntityDescription(
|
||||
key="demand_window",
|
||||
name=f"{entry.title} - Demand Window",
|
||||
translation_key="demand_window",
|
||||
)
|
||||
async_add_entities(
|
||||
[
|
||||
AmberPriceSpikeBinarySensor(coordinator, price_spike_description),
|
||||
AmberDemandWindowBinarySensor(coordinator, demand_window_description),
|
||||
]
|
||||
)
|
||||
|
@@ -111,6 +111,9 @@ class AmberUpdateCoordinator(DataUpdateCoordinator):
|
||||
]
|
||||
result["grid"]["renewables"] = round(general[0].renewables)
|
||||
result["grid"]["price_spike"] = general[0].spike_status.value
|
||||
tariff_information = general[0].tariff_information
|
||||
if tariff_information:
|
||||
result["grid"]["demand_window"] = tariff_information.demand_window
|
||||
|
||||
controlled_load = [
|
||||
interval for interval in current if is_controlled_load(interval)
|
||||
|
@@ -13,6 +13,14 @@
|
||||
"renewables": {
|
||||
"default": "mdi:solar-power"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
"demand_window": {
|
||||
"default": "mdi:meter-electric",
|
||||
"state": {
|
||||
"off": "mdi:meter-electric-outline"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/amberelectric",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["amberelectric"],
|
||||
"requirements": ["amberelectric==1.1.0"]
|
||||
"requirements": ["amberelectric==1.1.1"]
|
||||
}
|
||||
|
@@ -17,7 +17,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
import homeassistant.helpers.device_registry as dr
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
@@ -25,7 +24,6 @@ import homeassistant.helpers.entity_registry as er
|
||||
from .const import (
|
||||
ATTR_LAST_DATA,
|
||||
CONF_APP_KEY,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
TYPE_SOLARRADIATION,
|
||||
TYPE_SOLARRADIATION_LX,
|
||||
@@ -37,7 +35,6 @@ DATA_CONFIG = "config"
|
||||
|
||||
DEFAULT_SOCKET_MIN_RETRY = 15
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
type AmbientStationConfigEntry = ConfigEntry[AmbientStation]
|
||||
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aioambient import API
|
||||
from aioambient.errors import AmbientError
|
||||
import voluptuous as vol
|
||||
@@ -32,7 +34,9 @@ class AmbientStationFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors if errors else {},
|
||||
)
|
||||
|
||||
async def async_step_user(self, user_input: dict | None = None) -> ConfigFlowResult:
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the start of the config flow."""
|
||||
if not user_input:
|
||||
return await self._show_form()
|
||||
|
@@ -8,6 +8,7 @@ from datetime import timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
from amcrest import AmcrestError
|
||||
from haffmpeg.camera import CameraMjpeg
|
||||
@@ -244,7 +245,9 @@ class AmcrestCam(Camera):
|
||||
websession = async_get_clientsession(self.hass)
|
||||
streaming_url = self._api.mjpeg_url(typeno=self._resolution)
|
||||
stream_coro = websession.get(
|
||||
streaming_url, auth=self._token, timeout=CAMERA_WEB_SESSION_TIMEOUT
|
||||
streaming_url,
|
||||
auth=self._token,
|
||||
timeout=aiohttp.ClientTimeout(total=CAMERA_WEB_SESSION_TIMEOUT),
|
||||
)
|
||||
|
||||
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||
@@ -496,7 +499,7 @@ class AmcrestCam(Camera):
|
||||
await getattr(self, f"_async_set_{func}")(value)
|
||||
new_value = await getattr(self, f"_async_get_{func}")()
|
||||
if new_value != value:
|
||||
raise AmcrestCommandFailed
|
||||
raise AmcrestCommandFailed # noqa: TRY301
|
||||
except (AmcrestError, AmcrestCommandFailed) as error:
|
||||
if tries == 1:
|
||||
log_update_error(_LOGGER, action, self.name, description, error)
|
||||
|
@@ -7,6 +7,6 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["python_homeassistant_analytics"],
|
||||
"requirements": ["python-homeassistant-analytics==0.6.0"],
|
||||
"requirements": ["python-homeassistant-analytics==0.7.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
@@ -27,9 +26,6 @@ PLATFORMS: list[Platform] = [
|
||||
]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Android IP Webcam from a config entry."""
|
||||
websession = async_get_clientsession(hass)
|
||||
|
@@ -87,7 +87,7 @@ async def async_setup_entry(
|
||||
"adb_command",
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_LEARN_SENDEVENT, {}, "learn_sendevent"
|
||||
SERVICE_LEARN_SENDEVENT, None, "learn_sendevent"
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_DOWNLOAD,
|
||||
|
@@ -101,7 +101,7 @@
|
||||
},
|
||||
"learn_sendevent": {
|
||||
"name": "Learn sendevent",
|
||||
"description": "Translates a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of calling this service."
|
||||
"description": "Translates a key press on a remote into ADB 'sendevent' commands. You must press one button on the remote within 8 seconds of performing this action."
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/anova",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.14.0"]
|
||||
"requirements": ["anova-wifi==0.17.0"]
|
||||
}
|
||||
|
46
homeassistant/components/anthropic/__init__.py
Normal file
46
homeassistant/components/anthropic/__init__.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""The Anthropic integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import anthropic
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
PLATFORMS = (Platform.CONVERSATION,)
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
type AnthropicConfigEntry = ConfigEntry[anthropic.AsyncClient]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) -> bool:
|
||||
"""Set up Anthropic from a config entry."""
|
||||
client = anthropic.AsyncAnthropic(api_key=entry.data[CONF_API_KEY])
|
||||
try:
|
||||
await client.messages.create(
|
||||
model="claude-3-haiku-20240307",
|
||||
max_tokens=1,
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
timeout=10.0,
|
||||
)
|
||||
except anthropic.AuthenticationError as err:
|
||||
LOGGER.error("Invalid API key: %s", err)
|
||||
return False
|
||||
except anthropic.AnthropicError as err:
|
||||
raise ConfigEntryNotReady(err) from err
|
||||
|
||||
entry.runtime_data = client
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload Anthropic."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
210
homeassistant/components/anthropic/config_flow.py
Normal file
210
homeassistant/components/anthropic/config_flow.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""Config flow for Anthropic integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from types import MappingProxyType
|
||||
from typing import Any
|
||||
|
||||
import anthropic
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import llm
|
||||
from homeassistant.helpers.selector import (
|
||||
NumberSelector,
|
||||
NumberSelectorConfig,
|
||||
SelectOptionDict,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
TemplateSelector,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_RECOMMENDED,
|
||||
CONF_TEMPERATURE,
|
||||
DOMAIN,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_API_KEY): str,
|
||||
}
|
||||
)
|
||||
|
||||
RECOMMENDED_OPTIONS = {
|
||||
CONF_RECOMMENDED: True,
|
||||
CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
|
||||
CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
|
||||
}
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
client = anthropic.AsyncAnthropic(api_key=data[CONF_API_KEY])
|
||||
await client.messages.create(
|
||||
model="claude-3-haiku-20240307",
|
||||
max_tokens=1,
|
||||
messages=[{"role": "user", "content": "Hi"}],
|
||||
timeout=10.0,
|
||||
)
|
||||
|
||||
|
||||
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Anthropic."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors = {}
|
||||
|
||||
if user_input is not None:
|
||||
try:
|
||||
await validate_input(self.hass, user_input)
|
||||
except anthropic.APITimeoutError:
|
||||
errors["base"] = "timeout_connect"
|
||||
except anthropic.APIConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except anthropic.APIStatusError as e:
|
||||
if isinstance(e.body, dict):
|
||||
errors["base"] = e.body.get("error", {}).get("type", "unknown")
|
||||
else:
|
||||
errors["base"] = "unknown"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title="Claude",
|
||||
data=user_input,
|
||||
options=RECOMMENDED_OPTIONS,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors or None
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> OptionsFlow:
|
||||
"""Create the options flow."""
|
||||
return AnthropicOptionsFlow(config_entry)
|
||||
|
||||
|
||||
class AnthropicOptionsFlow(OptionsFlow):
|
||||
"""Anthropic config flow options handler."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
self.last_rendered_recommended = config_entry.options.get(
|
||||
CONF_RECOMMENDED, False
|
||||
)
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the options."""
|
||||
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
|
||||
|
||||
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)
|
||||
|
||||
# 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],
|
||||
}
|
||||
|
||||
suggested_values = options.copy()
|
||||
if not suggested_values.get(CONF_PROMPT):
|
||||
suggested_values[CONF_PROMPT] = llm.DEFAULT_INSTRUCTIONS_PROMPT
|
||||
|
||||
schema = self.add_suggested_values_to_schema(
|
||||
vol.Schema(anthropic_config_option_schema(self.hass, options)),
|
||||
suggested_values,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=schema,
|
||||
)
|
||||
|
||||
|
||||
def anthropic_config_option_schema(
|
||||
hass: HomeAssistant,
|
||||
options: dict[str, Any] | MappingProxyType[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.Required(
|
||||
CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
|
||||
): bool,
|
||||
}
|
||||
|
||||
if options.get(CONF_RECOMMENDED):
|
||||
return schema
|
||||
|
||||
schema.update(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_CHAT_MODEL,
|
||||
default=RECOMMENDED_CHAT_MODEL,
|
||||
): str,
|
||||
vol.Optional(
|
||||
CONF_MAX_TOKENS,
|
||||
default=RECOMMENDED_MAX_TOKENS,
|
||||
): int,
|
||||
vol.Optional(
|
||||
CONF_TEMPERATURE,
|
||||
default=RECOMMENDED_TEMPERATURE,
|
||||
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
||||
}
|
||||
)
|
||||
return schema
|
15
homeassistant/components/anthropic/const.py
Normal file
15
homeassistant/components/anthropic/const.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Constants for the Anthropic integration."""
|
||||
|
||||
import logging
|
||||
|
||||
DOMAIN = "anthropic"
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
CONF_RECOMMENDED = "recommended"
|
||||
CONF_PROMPT = "prompt"
|
||||
CONF_CHAT_MODEL = "chat_model"
|
||||
RECOMMENDED_CHAT_MODEL = "claude-3-5-sonnet-20240620"
|
||||
CONF_MAX_TOKENS = "max_tokens"
|
||||
RECOMMENDED_MAX_TOKENS = 1024
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
RECOMMENDED_TEMPERATURE = 1.0
|
316
homeassistant/components/anthropic/conversation.py
Normal file
316
homeassistant/components/anthropic/conversation.py
Normal file
@@ -0,0 +1,316 @@
|
||||
"""Conversation support for Anthropic."""
|
||||
|
||||
from collections.abc import Callable
|
||||
import json
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
import anthropic
|
||||
from anthropic._types import NOT_GIVEN
|
||||
from anthropic.types import (
|
||||
Message,
|
||||
MessageParam,
|
||||
TextBlock,
|
||||
TextBlockParam,
|
||||
ToolParam,
|
||||
ToolResultBlockParam,
|
||||
ToolUseBlock,
|
||||
ToolUseBlockParam,
|
||||
)
|
||||
import voluptuous as vol
|
||||
from voluptuous_openapi import convert
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.conversation import trace
|
||||
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, TemplateError
|
||||
from homeassistant.helpers import device_registry as dr, intent, llm, template
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import ulid
|
||||
|
||||
from . import AnthropicConfigEntry
|
||||
from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PROMPT,
|
||||
CONF_TEMPERATURE,
|
||||
DOMAIN,
|
||||
LOGGER,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_TEMPERATURE,
|
||||
)
|
||||
|
||||
# Max number of back and forth with the LLM to generate a response
|
||||
MAX_TOOL_ITERATIONS = 10
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: AnthropicConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up conversation entities."""
|
||||
agent = AnthropicConversationEntity(config_entry)
|
||||
async_add_entities([agent])
|
||||
|
||||
|
||||
def _format_tool(
|
||||
tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None
|
||||
) -> ToolParam:
|
||||
"""Format tool specification."""
|
||||
return ToolParam(
|
||||
name=tool.name,
|
||||
description=tool.description or "",
|
||||
input_schema=convert(tool.parameters, custom_serializer=custom_serializer),
|
||||
)
|
||||
|
||||
|
||||
def _message_convert(
|
||||
message: Message,
|
||||
) -> MessageParam:
|
||||
"""Convert from class to TypedDict."""
|
||||
param_content: list[TextBlockParam | ToolUseBlockParam] = []
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
return MessageParam(role=message.role, content=param_content)
|
||||
|
||||
|
||||
class AnthropicConversationEntity(
|
||||
conversation.ConversationEntity, conversation.AbstractConversationAgent
|
||||
):
|
||||
"""Anthropic conversation agent."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, entry: AnthropicConfigEntry) -> None:
|
||||
"""Initialize the agent."""
|
||||
self.entry = entry
|
||||
self.history: dict[str, list[MessageParam]] = {}
|
||||
self._attr_unique_id = entry.entry_id
|
||||
self._attr_device_info = dr.DeviceInfo(
|
||||
identifiers={(DOMAIN, entry.entry_id)},
|
||||
manufacturer="Anthropic",
|
||||
model="Claude",
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
)
|
||||
if self.entry.options.get(CONF_LLM_HASS_API):
|
||||
self._attr_supported_features = (
|
||||
conversation.ConversationEntityFeature.CONTROL
|
||||
)
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str] | Literal["*"]:
|
||||
"""Return a list of supported languages."""
|
||||
return MATCH_ALL
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""When entity is added to Home Assistant."""
|
||||
await super().async_added_to_hass()
|
||||
self.entry.async_on_unload(
|
||||
self.entry.add_update_listener(self._async_entry_update_listener)
|
||||
)
|
||||
|
||||
async def async_process(
|
||||
self, user_input: conversation.ConversationInput
|
||||
) -> conversation.ConversationResult:
|
||||
"""Process a sentence."""
|
||||
options = self.entry.options
|
||||
intent_response = intent.IntentResponse(language=user_input.language)
|
||||
llm_api: llm.APIInstance | None = None
|
||||
tools: list[ToolParam] | None = None
|
||||
user_name: str | None = None
|
||||
llm_context = llm.LLMContext(
|
||||
platform=DOMAIN,
|
||||
context=user_input.context,
|
||||
user_prompt=user_input.text,
|
||||
language=user_input.language,
|
||||
assistant=conversation.DOMAIN,
|
||||
device_id=user_input.device_id,
|
||||
)
|
||||
|
||||
if options.get(CONF_LLM_HASS_API):
|
||||
try:
|
||||
llm_api = await llm.async_get_api(
|
||||
self.hass,
|
||||
options[CONF_LLM_HASS_API],
|
||||
llm_context,
|
||||
)
|
||||
except HomeAssistantError as err:
|
||||
LOGGER.error("Error getting LLM API: %s", err)
|
||||
intent_response.async_set_error(
|
||||
intent.IntentResponseErrorCode.UNKNOWN,
|
||||
f"Error preparing LLM API: {err}",
|
||||
)
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=user_input.conversation_id
|
||||
)
|
||||
tools = [
|
||||
_format_tool(tool, llm_api.custom_serializer) for tool in llm_api.tools
|
||||
]
|
||||
|
||||
if user_input.conversation_id is None:
|
||||
conversation_id = ulid.ulid_now()
|
||||
messages = []
|
||||
|
||||
elif user_input.conversation_id in self.history:
|
||||
conversation_id = user_input.conversation_id
|
||||
messages = self.history[conversation_id]
|
||||
|
||||
else:
|
||||
# Conversation IDs are ULIDs. We generate a new one if not provided.
|
||||
# If an old OLID is passed in, we will generate a new one to indicate
|
||||
# a new conversation was started. If the user picks their own, they
|
||||
# want to track a conversation and we respect it.
|
||||
try:
|
||||
ulid.ulid_to_bytes(user_input.conversation_id)
|
||||
conversation_id = ulid.ulid_now()
|
||||
except ValueError:
|
||||
conversation_id = user_input.conversation_id
|
||||
|
||||
messages = []
|
||||
|
||||
if (
|
||||
user_input.context
|
||||
and user_input.context.user_id
|
||||
and (
|
||||
user := await self.hass.auth.async_get_user(user_input.context.user_id)
|
||||
)
|
||||
):
|
||||
user_name = user.name
|
||||
|
||||
try:
|
||||
prompt_parts = [
|
||||
template.Template(
|
||||
llm.BASE_PROMPT
|
||||
+ options.get(CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT),
|
||||
self.hass,
|
||||
).async_render(
|
||||
{
|
||||
"ha_name": self.hass.config.location_name,
|
||||
"user_name": user_name,
|
||||
"llm_context": llm_context,
|
||||
},
|
||||
parse_result=False,
|
||||
)
|
||||
]
|
||||
|
||||
except TemplateError as err:
|
||||
LOGGER.error("Error rendering prompt: %s", err)
|
||||
intent_response.async_set_error(
|
||||
intent.IntentResponseErrorCode.UNKNOWN,
|
||||
f"Sorry, I had a problem with my template: {err}",
|
||||
)
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=conversation_id
|
||||
)
|
||||
|
||||
if llm_api:
|
||||
prompt_parts.append(llm_api.api_prompt)
|
||||
|
||||
prompt = "\n".join(prompt_parts)
|
||||
|
||||
# Create a copy of the variable because we attach it to the trace
|
||||
messages = [*messages, MessageParam(role="user", content=user_input.text)]
|
||||
|
||||
LOGGER.debug("Prompt: %s", messages)
|
||||
LOGGER.debug("Tools: %s", tools)
|
||||
trace.async_conversation_trace_append(
|
||||
trace.ConversationTraceEventType.AGENT_DETAIL,
|
||||
{"system": prompt, "messages": messages},
|
||||
)
|
||||
|
||||
client = self.entry.runtime_data
|
||||
|
||||
# To prevent infinite loops, we limit the number of iterations
|
||||
for _iteration in range(MAX_TOOL_ITERATIONS):
|
||||
try:
|
||||
response = 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=prompt,
|
||||
temperature=options.get(CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE),
|
||||
)
|
||||
except anthropic.AnthropicError as err:
|
||||
intent_response.async_set_error(
|
||||
intent.IntentResponseErrorCode.UNKNOWN,
|
||||
f"Sorry, I had a problem talking to Anthropic: {err}",
|
||||
)
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=conversation_id
|
||||
)
|
||||
|
||||
LOGGER.debug("Response %s", response)
|
||||
|
||||
messages.append(_message_convert(response))
|
||||
|
||||
if response.stop_reason != "tool_use" or not llm_api:
|
||||
break
|
||||
|
||||
tool_results: list[ToolResultBlockParam] = []
|
||||
for tool_call in response.content:
|
||||
if isinstance(tool_call, TextBlock):
|
||||
LOGGER.info(tool_call.text)
|
||||
|
||||
if not isinstance(tool_call, ToolUseBlock):
|
||||
continue
|
||||
|
||||
tool_input = llm.ToolInput(
|
||||
tool_name=tool_call.name,
|
||||
tool_args=cast(dict[str, Any], tool_call.input),
|
||||
)
|
||||
LOGGER.debug(
|
||||
"Tool call: %s(%s)", tool_input.tool_name, tool_input.tool_args
|
||||
)
|
||||
|
||||
try:
|
||||
tool_response = await llm_api.async_call_tool(tool_input)
|
||||
except (HomeAssistantError, vol.Invalid) as e:
|
||||
tool_response = {"error": type(e).__name__}
|
||||
if str(e):
|
||||
tool_response["error_text"] = str(e)
|
||||
|
||||
LOGGER.debug("Tool response: %s", tool_response)
|
||||
tool_results.append(
|
||||
ToolResultBlockParam(
|
||||
type="tool_result",
|
||||
tool_use_id=tool_call.id,
|
||||
content=json.dumps(tool_response),
|
||||
)
|
||||
)
|
||||
|
||||
messages.append(MessageParam(role="user", content=tool_results))
|
||||
|
||||
self.history[conversation_id] = messages
|
||||
|
||||
for content in response.content:
|
||||
if isinstance(content, TextBlock):
|
||||
intent_response.async_set_speech(content.text)
|
||||
break
|
||||
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response, conversation_id=conversation_id
|
||||
)
|
||||
|
||||
async def _async_entry_update_listener(
|
||||
self, hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
# Reload as we update device info + entity name + supported features
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
12
homeassistant/components/anthropic/manifest.json
Normal file
12
homeassistant/components/anthropic/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"domain": "anthropic",
|
||||
"name": "Anthropic Conversation",
|
||||
"after_dependencies": ["assist_pipeline", "intent"],
|
||||
"codeowners": ["@Shulyaka"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["conversation"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/anthropic",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["anthropic==0.31.2"]
|
||||
}
|
34
homeassistant/components/anthropic/strings.json
Normal file
34
homeassistant/components/anthropic/strings.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
|
||||
"authentication_error": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"prompt": "Instructions",
|
||||
"chat_model": "[%key:common::generic::model%]",
|
||||
"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"
|
||||
},
|
||||
"data_description": {
|
||||
"prompt": "Instruct how the LLM should respond. This can be a template."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,8 +16,6 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_STATE_CHANGED,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Event, EventStateChangedData, HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@@ -121,7 +119,7 @@ class KafkaManager:
|
||||
state = event.data["new_state"]
|
||||
if (
|
||||
state is None
|
||||
or state.state in (STATE_UNKNOWN, "", STATE_UNAVAILABLE)
|
||||
or state.state == ""
|
||||
or not self._entities_filter(state.entity_id)
|
||||
):
|
||||
return None
|
||||
@@ -141,7 +139,8 @@ class KafkaManager:
|
||||
|
||||
async def write(self, event: Event[EventStateChangedData]) -> None:
|
||||
"""Write a binary payload to Kafka."""
|
||||
key = event.data["entity_id"].encode("utf-8")
|
||||
payload = self._encode_event(event)
|
||||
|
||||
if payload:
|
||||
await self._producer.send_and_wait(self._topic, payload)
|
||||
await self._producer.send_and_wait(self._topic, payload, key)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user