mirror of
https://github.com/home-assistant/core.git
synced 2025-09-22 11:29:33 +00:00
Compare commits
2069 Commits
2024.10.0b
...
cloud_enab
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b23eacc7ad | ||
![]() |
5293fc73d8 | ||
![]() |
870bf388e0 | ||
![]() |
7a4dac1eb1 | ||
![]() |
88480d154a | ||
![]() |
5497c440d9 | ||
![]() |
1e26cf13d6 | ||
![]() |
0dd208a4b9 | ||
![]() |
c3492bc0ed | ||
![]() |
85bf8d1374 | ||
![]() |
e040eb0ff2 | ||
![]() |
d7f41ff8a9 | ||
![]() |
de5437f61e | ||
![]() |
c52a893e21 | ||
![]() |
f7f1830b7e | ||
![]() |
784ad20fb6 | ||
![]() |
0468e7e7a3 | ||
![]() |
88c227681d | ||
![]() |
3a37ff13a6 | ||
![]() |
73929e6791 | ||
![]() |
980b0fa5e6 | ||
![]() |
fbc4a87166 | ||
![]() |
7f9ec2a79e | ||
![]() |
d8b55d39e4 | ||
![]() |
ee41725b53 | ||
![]() |
ae1203336d | ||
![]() |
f10063c9be | ||
![]() |
1da4579a09 | ||
![]() |
7fd9339ad8 | ||
![]() |
de391fa98b | ||
![]() |
70211ab78e | ||
![]() |
a1a08f7755 | ||
![]() |
433321136d | ||
![]() |
0677bba5bd | ||
![]() |
d0ad834d93 | ||
![]() |
7d2d6a82b0 | ||
![]() |
e8dc62411a | ||
![]() |
7925007ab4 | ||
![]() |
7515deddab | ||
![]() |
e382f924e6 | ||
![]() |
7fdcb98518 | ||
![]() |
d0dbca41f7 | ||
![]() |
f3229c723c | ||
![]() |
cafa598fd6 | ||
![]() |
73a62a09b0 | ||
![]() |
ecd8dde347 | ||
![]() |
31a2bb1b98 | ||
![]() |
0fc019305e | ||
![]() |
adb1c59859 | ||
![]() |
5d0277a0d1 | ||
![]() |
21d81d5a5c | ||
![]() |
0de4bfcc2c | ||
![]() |
2cc5486794 | ||
![]() |
e3315383ab | ||
![]() |
31b505828b | ||
![]() |
b61580a937 | ||
![]() |
928e5348e4 | ||
![]() |
622682eb43 | ||
![]() |
97fa568876 | ||
![]() |
c10f078f2a | ||
![]() |
e6d16f06fc | ||
![]() |
c89ab7a142 | ||
![]() |
6837ea947c | ||
![]() |
5f0f29704b | ||
![]() |
1f43dc6676 | ||
![]() |
4d7405de2c | ||
![]() |
4adffdd1a6 | ||
![]() |
4e2f5bdb7d | ||
![]() |
03bc711c51 | ||
![]() |
8b8e949bdf | ||
![]() |
69ba0d3a50 | ||
![]() |
25fb70f281 | ||
![]() |
0304588bb8 | ||
![]() |
08f5081197 | ||
![]() |
701f35488c | ||
![]() |
d11012b2b7 | ||
![]() |
8384100e1b | ||
![]() |
cd0349ee4d | ||
![]() |
b413e481cb | ||
![]() |
9f7e6048f8 | ||
![]() |
2802b77f21 | ||
![]() |
964ad43a27 | ||
![]() |
182be6e0ea | ||
![]() |
cd11f01ace | ||
![]() |
742eca5927 | ||
![]() |
48e7fed901 | ||
![]() |
0a4c0fe7cc | ||
![]() |
9037cb8a7d | ||
![]() |
c97cc34879 | ||
![]() |
1ac9217630 | ||
![]() |
e4036a2f14 | ||
![]() |
da9c73a767 | ||
![]() |
e4aaaf10c3 | ||
![]() |
a7be76ba0a | ||
![]() |
f7cc91903c | ||
![]() |
4a8a674bd3 | ||
![]() |
a8db25fbd8 | ||
![]() |
2dc81ed866 | ||
![]() |
c4762f3ff4 | ||
![]() |
14285973b8 | ||
![]() |
353ccf3ea7 | ||
![]() |
6b90d8ff1a | ||
![]() |
51e691f832 | ||
![]() |
6c7ac7a6ef | ||
![]() |
52ed1bf44a | ||
![]() |
3eab0b704e | ||
![]() |
1f32e02ba2 | ||
![]() |
074418f8f7 | ||
![]() |
b711b17193 | ||
![]() |
03c3d09583 | ||
![]() |
f49547d598 | ||
![]() |
7678be8e2b | ||
![]() |
7672215095 | ||
![]() |
18cf96b92b | ||
![]() |
94d597fd41 | ||
![]() |
24b47b50ea | ||
![]() |
e3dfa84d65 | ||
![]() |
ed1366f463 | ||
![]() |
5d5908a03f | ||
![]() |
3062bad19e | ||
![]() |
28832cbd3e | ||
![]() |
ce94073321 | ||
![]() |
fa61e02207 | ||
![]() |
d1dab83f10 | ||
![]() |
2b7d593ebe | ||
![]() |
e407b4730d | ||
![]() |
0d19e85a0d | ||
![]() |
dac6271e01 | ||
![]() |
8cae8edc55 | ||
![]() |
a3b0909e3f | ||
![]() |
ee30520b57 | ||
![]() |
536e686892 | ||
![]() |
ef767c2b9f | ||
![]() |
c1ecc13cb3 | ||
![]() |
c5e3ba536c | ||
![]() |
0e324c074a | ||
![]() |
a3ba7803db | ||
![]() |
49bf5db5ff | ||
![]() |
50981c26ad | ||
![]() |
2adbf7c933 | ||
![]() |
838ef0bb9f | ||
![]() |
43c2658962 | ||
![]() |
bbefa971d8 | ||
![]() |
cb97f2f13c | ||
![]() |
a657b9bb84 | ||
![]() |
2d2f55a4df | ||
![]() |
df16e6d022 | ||
![]() |
56212c6fa5 | ||
![]() |
bc964ce7f0 | ||
![]() |
ed4f55406c | ||
![]() |
03d5b18974 | ||
![]() |
53c486ccd1 | ||
![]() |
9a2a177b28 | ||
![]() |
18e12740d9 | ||
![]() |
5a24b670a2 | ||
![]() |
94c5c8f42e | ||
![]() |
e84d5fba11 | ||
![]() |
b808c0c5eb | ||
![]() |
782417528c | ||
![]() |
7757423d18 | ||
![]() |
e5a28f4f25 | ||
![]() |
c18d50910f | ||
![]() |
d4adb1f298 | ||
![]() |
fe0a822721 | ||
![]() |
9f427893b1 | ||
![]() |
3b840c684b | ||
![]() |
bc84fdc64a | ||
![]() |
401262c23d | ||
![]() |
795384ca2d | ||
![]() |
dfc3423c83 | ||
![]() |
22b5071c26 | ||
![]() |
4b9524c5c1 | ||
![]() |
9cd46c7f03 | ||
![]() |
232a6868ff | ||
![]() |
361e0d4fc7 | ||
![]() |
26d8d5343a | ||
![]() |
995aab8347 | ||
![]() |
399011552b | ||
![]() |
0c9f30364c | ||
![]() |
bdc17621ee | ||
![]() |
399c53a57e | ||
![]() |
f55e13bde4 | ||
![]() |
dea31e5744 | ||
![]() |
48d9df89ac | ||
![]() |
adf836d9ac | ||
![]() |
51d6948848 | ||
![]() |
7ce74cb5ec | ||
![]() |
29ba140816 | ||
![]() |
0ca4f3e1ba | ||
![]() |
0430e6794e | ||
![]() |
29fa7f827a | ||
![]() |
57d1001603 | ||
![]() |
96de4b3828 | ||
![]() |
c6cb2884f4 | ||
![]() |
27e81fe0ed | ||
![]() |
2c1db10986 | ||
![]() |
a7ba4bd086 | ||
![]() |
25449b424f | ||
![]() |
f6f89bd807 | ||
![]() |
370d7d6bdf | ||
![]() |
4dbf3359c1 | ||
![]() |
25eb7173bf | ||
![]() |
648c3d500b | ||
![]() |
33016c2977 | ||
![]() |
5679b061d2 | ||
![]() |
2eb2bdd615 | ||
![]() |
184cbfea23 | ||
![]() |
f88bc008e5 | ||
![]() |
a927312fb5 | ||
![]() |
5f13db2356 | ||
![]() |
64e84e2aa0 | ||
![]() |
901457e7aa | ||
![]() |
89a9c2ec24 | ||
![]() |
9e04457472 | ||
![]() |
6ecdbb677f | ||
![]() |
211ce43127 | ||
![]() |
f5555df990 | ||
![]() |
82c2422990 | ||
![]() |
734ebc1adb | ||
![]() |
eb3371beef | ||
![]() |
e1ef1063fe | ||
![]() |
c355a53485 | ||
![]() |
79de1d9ed4 | ||
![]() |
7fefa5c235 | ||
![]() |
94db78a0be | ||
![]() |
83a1b06b56 | ||
![]() |
1e42a38473 | ||
![]() |
c54ed53a81 | ||
![]() |
611a952232 | ||
![]() |
05e76105ad | ||
![]() |
ed56e5d631 | ||
![]() |
9253fa4471 | ||
![]() |
c85eb6bf8e | ||
![]() |
cc30d34e87 | ||
![]() |
14875a1101 | ||
![]() |
030aebb97f | ||
![]() |
6e2f36b6d4 | ||
![]() |
25a05eb156 | ||
![]() |
b71c4377f6 | ||
![]() |
d671341864 | ||
![]() |
383f712d43 | ||
![]() |
8a20cd77a0 | ||
![]() |
14023644ef | ||
![]() |
496fc42b94 | ||
![]() |
da0688ce8e | ||
![]() |
89d3707cb7 | ||
![]() |
3f5e395e2f | ||
![]() |
00ea1cab9f | ||
![]() |
5f36062ef3 | ||
![]() |
e562b6f42b | ||
![]() |
b76a94bd42 | ||
![]() |
4e11ff05de | ||
![]() |
080e3d7a42 | ||
![]() |
69e3348cd7 | ||
![]() |
6caa4baa00 | ||
![]() |
4729b19dc6 | ||
![]() |
8abbc4abbc | ||
![]() |
3a667bce8c | ||
![]() |
4c86102daf | ||
![]() |
15bf652f37 | ||
![]() |
eafed2b86c | ||
![]() |
79901cede9 | ||
![]() |
27dc82d7d0 | ||
![]() |
ae37c8cc7a | ||
![]() |
5eadfcc524 | ||
![]() |
5fd1e23255 | ||
![]() |
72bcc6702f | ||
![]() |
8889464e04 | ||
![]() |
af58b0c3b7 | ||
![]() |
e9e20229a3 | ||
![]() |
80ff6dc618 | ||
![]() |
fa30100160 | ||
![]() |
e6c20333b3 | ||
![]() |
3858400a6f | ||
![]() |
95eefbac20 | ||
![]() |
e1e731eb48 | ||
![]() |
f7ce4ff25c | ||
![]() |
c7b2ffbc8e | ||
![]() |
3a1502e2bb | ||
![]() |
b830f83a34 | ||
![]() |
2982e733bc | ||
![]() |
e89ce215c6 | ||
![]() |
b6345f8d07 | ||
![]() |
9d261bab48 | ||
![]() |
b6f875134e | ||
![]() |
90ceebdf91 | ||
![]() |
617e87e02c | ||
![]() |
dafd54ba2b | ||
![]() |
e8c3539709 | ||
![]() |
e5263dc0c8 | ||
![]() |
3584c710b9 | ||
![]() |
0b56ef5699 | ||
![]() |
90bd9bb626 | ||
![]() |
03e6a13896 | ||
![]() |
9fb3261f02 | ||
![]() |
0bc6b8b0d4 | ||
![]() |
18d2ced045 | ||
![]() |
6c75e0bee1 | ||
![]() |
0b981f42bb | ||
![]() |
82868a8588 | ||
![]() |
6e93777f54 | ||
![]() |
9349292464 | ||
![]() |
7084b3b52c | ||
![]() |
0f0f5fd0ab | ||
![]() |
cb0b942db3 | ||
![]() |
b1c9f83952 | ||
![]() |
1ff0efc97b | ||
![]() |
a4da2a9eb5 | ||
![]() |
ba3cfb5f87 | ||
![]() |
bf196935f6 | ||
![]() |
6e98343706 | ||
![]() |
de453ab5c1 | ||
![]() |
f408de4fc3 | ||
![]() |
7863927c3a | ||
![]() |
9fcf757021 | ||
![]() |
fc0547ccdf | ||
![]() |
22f8f117fb | ||
![]() |
2052579efc | ||
![]() |
b8f2583bc3 | ||
![]() |
6323a078e1 | ||
![]() |
ca0be3ec8a | ||
![]() |
91157c21ef | ||
![]() |
cc4fae10f5 | ||
![]() |
d180ff417d | ||
![]() |
8870b657d1 | ||
![]() |
81735b7b47 | ||
![]() |
7fd261347b | ||
![]() |
df796d432e | ||
![]() |
f6e36615d6 | ||
![]() |
0278735dbf | ||
![]() |
9c8d8fef16 | ||
![]() |
6897b24c10 | ||
![]() |
a2a3f59e65 | ||
![]() |
2626a74840 | ||
![]() |
689260f581 | ||
![]() |
f1a2c8be4b | ||
![]() |
0579d565dd | ||
![]() |
f141f5f908 | ||
![]() |
0c25252d9f | ||
![]() |
400b377aa8 | ||
![]() |
a5f3c434e0 | ||
![]() |
365f8046ac | ||
![]() |
4ac35d40cd | ||
![]() |
7691991a93 | ||
![]() |
d0c45b1857 | ||
![]() |
02750452df | ||
![]() |
41a81cbf15 | ||
![]() |
ff621d5bf3 | ||
![]() |
6d561a9796 | ||
![]() |
4784199038 | ||
![]() |
df35c8e707 | ||
![]() |
57eeaf1f75 | ||
![]() |
3cadc1796f | ||
![]() |
ae06f734ce | ||
![]() |
08a53362a7 | ||
![]() |
274c928ec0 | ||
![]() |
d75dda0c05 | ||
![]() |
0c40fcdaeb | ||
![]() |
0a1ba8a4a3 | ||
![]() |
018acc0a3c | ||
![]() |
3a293c6bc4 | ||
![]() |
9155d56190 | ||
![]() |
461dc13da9 | ||
![]() |
b48e2127b8 | ||
![]() |
11ab992dbb | ||
![]() |
4be2cdf90a | ||
![]() |
cdd5cb2876 | ||
![]() |
cdc67aa891 | ||
![]() |
6a22a2b867 | ||
![]() |
0883b23d0c | ||
![]() |
595459bfda | ||
![]() |
5141a4d292 | ||
![]() |
cf8b7607ae | ||
![]() |
b38fe00387 | ||
![]() |
5d446f0e14 | ||
![]() |
a592ece9c8 | ||
![]() |
9cb60c61d1 | ||
![]() |
90ed06c354 | ||
![]() |
22d64cb8f4 | ||
![]() |
453039e860 | ||
![]() |
e727162225 | ||
![]() |
a898a5996e | ||
![]() |
d501bb8d52 | ||
![]() |
7ab8ff56b3 | ||
![]() |
eda36512ec | ||
![]() |
04aee812f8 | ||
![]() |
6718cce203 | ||
![]() |
49f0bb6990 | ||
![]() |
38afcbb21f | ||
![]() |
87ab2beddf | ||
![]() |
a05a34239d | ||
![]() |
f11aba9648 | ||
![]() |
c2ef119e50 | ||
![]() |
8b6c99776e | ||
![]() |
463bffaeb6 | ||
![]() |
0cfd8032c0 | ||
![]() |
144d5ff0cc | ||
![]() |
ab5c65b08c | ||
![]() |
6b33bf3961 | ||
![]() |
89eb395e2d | ||
![]() |
d671d48869 | ||
![]() |
ed582fae91 | ||
![]() |
4d5c3ee0aa | ||
![]() |
02046fcdb4 | ||
![]() |
fbe27749a0 | ||
![]() |
eddab96a69 | ||
![]() |
ed3376352d | ||
![]() |
dfbb763031 | ||
![]() |
5cf13d9273 | ||
![]() |
5ef45fd12e | ||
![]() |
8a293a41f5 | ||
![]() |
931820a170 | ||
![]() |
e9944b964a | ||
![]() |
dbae1d2f8b | ||
![]() |
0dc8feba05 | ||
![]() |
5c7c2347f7 | ||
![]() |
d069907948 | ||
![]() |
725ab477a8 | ||
![]() |
d05ee9ff60 | ||
![]() |
3c1f6d97cc | ||
![]() |
5fe827f6c4 | ||
![]() |
76f9a93ed7 | ||
![]() |
df2506bfbb | ||
![]() |
b25ab04d2c | ||
![]() |
6f094e8a54 | ||
![]() |
e18ffc53f2 | ||
![]() |
0eea3176d6 | ||
![]() |
4f20977a8e | ||
![]() |
5bd63bb56b | ||
![]() |
f7103da818 | ||
![]() |
bf4922a7ef | ||
![]() |
6f7eac5c6d | ||
![]() |
d6e73a89f3 | ||
![]() |
269aefd405 | ||
![]() |
a6865f1639 | ||
![]() |
f55aa0b86e | ||
![]() |
02b34f05aa | ||
![]() |
37f42707e5 | ||
![]() |
17f3ba1434 | ||
![]() |
31dcc25ba5 | ||
![]() |
4da93f6a5e | ||
![]() |
5ed7d32749 | ||
![]() |
ab5b9dbdc9 | ||
![]() |
3b28bf07d1 | ||
![]() |
b626c9b450 | ||
![]() |
5430eca93e | ||
![]() |
b41c477f44 | ||
![]() |
5900413c08 | ||
![]() |
c2ceab741f | ||
![]() |
45ff4940eb | ||
![]() |
9c8a15cb64 | ||
![]() |
b09e54c961 | ||
![]() |
f44b7e202a | ||
![]() |
0f535e979f | ||
![]() |
4c2c01b4f6 | ||
![]() |
b1d48fe9a2 | ||
![]() |
41590f91ac | ||
![]() |
e9d1f4f46e | ||
![]() |
7f287412ba | ||
![]() |
2df094de2b | ||
![]() |
964ab5b351 | ||
![]() |
3f6e9a54fe | ||
![]() |
4ec5d5ae1e | ||
![]() |
c49b155c29 | ||
![]() |
fc602b1888 | ||
![]() |
81421992a2 | ||
![]() |
4ef31f9331 | ||
![]() |
d7e304badf | ||
![]() |
bf3f1b4b49 | ||
![]() |
2ac0ff03fc | ||
![]() |
d10553d624 | ||
![]() |
b1dfc3cd23 | ||
![]() |
696efe349e | ||
![]() |
6a32722acc | ||
![]() |
8eaec56c6b | ||
![]() |
60d3c9342d | ||
![]() |
4dc2433e8b | ||
![]() |
2bd5039f28 | ||
![]() |
8b1b14a704 | ||
![]() |
5e674ce1d0 | ||
![]() |
3656bcf752 | ||
![]() |
39093fc2bc | ||
![]() |
efa5838be4 | ||
![]() |
1c6ad2fa66 | ||
![]() |
af144e1b77 | ||
![]() |
b451bfed81 | ||
![]() |
3e32c50936 | ||
![]() |
208b15637a | ||
![]() |
c958cce769 | ||
![]() |
602ec54579 | ||
![]() |
fa2bfc5d9d | ||
![]() |
94f906b34c | ||
![]() |
60c93456c0 | ||
![]() |
a4f210379d | ||
![]() |
27e6205a37 | ||
![]() |
3db6d82904 | ||
![]() |
b8ddfd642e | ||
![]() |
c98acd42db | ||
![]() |
39f418f2d2 | ||
![]() |
9fbd484dfe | ||
![]() |
1773f2aadc | ||
![]() |
cb1b72d6ba | ||
![]() |
f5a2ec961d | ||
![]() |
bf40e77d65 | ||
![]() |
568bdef61f | ||
![]() |
2303521778 | ||
![]() |
3bf2946d13 | ||
![]() |
484e5cb3e8 | ||
![]() |
fbe8b6c34d | ||
![]() |
4e7397dc9d | ||
![]() |
a6189106e1 | ||
![]() |
ed6123a3e6 | ||
![]() |
0cd5deaa3f | ||
![]() |
6c047e2678 | ||
![]() |
405a480cae | ||
![]() |
b4e69bab71 | ||
![]() |
db81edfb2b | ||
![]() |
24829bc44f | ||
![]() |
c8594045df | ||
![]() |
ea3f9b971f | ||
![]() |
380974eed4 | ||
![]() |
8151403bf6 | ||
![]() |
16f5e76f00 | ||
![]() |
b6b178cac0 | ||
![]() |
0f020366e3 | ||
![]() |
27a19be369 | ||
![]() |
0c166eb307 | ||
![]() |
79d73c28a7 | ||
![]() |
2aed01b530 | ||
![]() |
3fb0d61271 | ||
![]() |
599acaf514 | ||
![]() |
5f4103a4a7 | ||
![]() |
c7c72231c7 | ||
![]() |
6887a4419e | ||
![]() |
db5cb6233c | ||
![]() |
963829712d | ||
![]() |
46ceccfbb3 | ||
![]() |
aaf3039967 | ||
![]() |
2509f18def | ||
![]() |
a1e2d79613 | ||
![]() |
96ba5c3983 | ||
![]() |
041282190a | ||
![]() |
8cdd5de75c | ||
![]() |
a95c232f11 | ||
![]() |
c9aba288b4 | ||
![]() |
35a9d502af | ||
![]() |
409c8783fe | ||
![]() |
3adc3d7732 | ||
![]() |
ec19712388 | ||
![]() |
2c89e89c84 | ||
![]() |
e602a464db | ||
![]() |
ffc0651d89 | ||
![]() |
7162efd836 | ||
![]() |
8e7d782102 | ||
![]() |
dc2028f99c | ||
![]() |
f12ba5f7a9 | ||
![]() |
45fb21e32d | ||
![]() |
ecbb417736 | ||
![]() |
3a59a862d5 | ||
![]() |
e34fab0045 | ||
![]() |
7254ebe0e3 | ||
![]() |
b43bc3f32d | ||
![]() |
ca3d13b5cc | ||
![]() |
c8818bcce3 | ||
![]() |
b234b5937a | ||
![]() |
1bdef0f2f7 | ||
![]() |
56fb61bd6f | ||
![]() |
2c7d0b8909 | ||
![]() |
cbb8d76da7 | ||
![]() |
cce925c06c | ||
![]() |
505a4bfc34 | ||
![]() |
58e151966c | ||
![]() |
8a6c9b7afc | ||
![]() |
e72e2071b0 | ||
![]() |
5d3af27928 | ||
![]() |
5dc0bedbc4 | ||
![]() |
8f7ae2665c | ||
![]() |
10fdf819d3 | ||
![]() |
02928601ef | ||
![]() |
c227f6dc2c | ||
![]() |
673f0224c9 | ||
![]() |
79c602f59c | ||
![]() |
07c070e253 | ||
![]() |
9bda3bd477 | ||
![]() |
2c9ad9562e | ||
![]() |
c264ee22e7 | ||
![]() |
f194a689cc | ||
![]() |
a36b350954 | ||
![]() |
db4278fb9d | ||
![]() |
39ba4cff2f | ||
![]() |
d68da74790 | ||
![]() |
5fc45cd736 | ||
![]() |
5ae2f3d081 | ||
![]() |
478bf643bf | ||
![]() |
7929895b11 | ||
![]() |
da11a72b4c | ||
![]() |
1649368cee | ||
![]() |
a528d62c16 | ||
![]() |
bd13dbdad0 | ||
![]() |
8e7ffd9e16 | ||
![]() |
f0bff09b5e | ||
![]() |
0e959b3019 | ||
![]() |
983cd9c3fc | ||
![]() |
2236ca3e12 | ||
![]() |
f3afa6a7d9 | ||
![]() |
ce7e2e3243 | ||
![]() |
13416825b1 | ||
![]() |
6c664e7ba9 | ||
![]() |
34359617b5 | ||
![]() |
9e2696b9bc | ||
![]() |
bf840e8bfa | ||
![]() |
1f03c140f5 | ||
![]() |
2de161ce0e | ||
![]() |
1171106afb | ||
![]() |
f57ae73071 | ||
![]() |
59872b5698 | ||
![]() |
7cd8ea00d1 | ||
![]() |
4b2f38926a | ||
![]() |
537c95cf29 | ||
![]() |
81a5722708 | ||
![]() |
c150b913ac | ||
![]() |
3e4b67db6c | ||
![]() |
d727f8ff50 | ||
![]() |
9546bf1dee | ||
![]() |
dd9ce34d18 | ||
![]() |
73f2d972e4 | ||
![]() |
7d699c6c35 | ||
![]() |
21f23f67f4 | ||
![]() |
8874ba2779 | ||
![]() |
420538e6e7 | ||
![]() |
8eb68b54d9 | ||
![]() |
80202f33cb | ||
![]() |
c24579bfb2 | ||
![]() |
21256c4529 | ||
![]() |
668626b920 | ||
![]() |
cbfa3bb56d | ||
![]() |
536fcf02d7 | ||
![]() |
a8ac3acbbe | ||
![]() |
7980155375 | ||
![]() |
aa855e31c8 | ||
![]() |
675ee8e813 | ||
![]() |
50ccce7387 | ||
![]() |
40b561ea69 | ||
![]() |
a0f73bd30f | ||
![]() |
1b7fcce42d | ||
![]() |
4749af6e90 | ||
![]() |
f7ad40263b | ||
![]() |
e5b25bfa58 | ||
![]() |
1d23adcda3 | ||
![]() |
0216d36ab7 | ||
![]() |
2bec20ad76 | ||
![]() |
93c1245b0f | ||
![]() |
72504d7619 | ||
![]() |
320aa34d39 | ||
![]() |
87f2a4242e | ||
![]() |
9bf0cbd659 | ||
![]() |
b1470fd9b8 | ||
![]() |
08016dc3b6 | ||
![]() |
7a448f5528 | ||
![]() |
4ac23bf14c | ||
![]() |
bc708dee30 | ||
![]() |
2888e5748e | ||
![]() |
88f0a33e69 | ||
![]() |
3165f92b6b | ||
![]() |
3bd0fca633 | ||
![]() |
cdff10d281 | ||
![]() |
e425741c34 | ||
![]() |
20a367b243 | ||
![]() |
fdded9e7ee | ||
![]() |
7d29bff136 | ||
![]() |
0abfbeed3c | ||
![]() |
35b7c3038a | ||
![]() |
46dd96a4b7 | ||
![]() |
788232ca35 | ||
![]() |
3b458738e0 | ||
![]() |
2c8fc67ab1 | ||
![]() |
9b3ed3ed72 | ||
![]() |
c59197e87a | ||
![]() |
03e3c88d8b | ||
![]() |
39693786ef | ||
![]() |
357c324df1 | ||
![]() |
650482208c | ||
![]() |
2acad4a78c | ||
![]() |
65ee4e1916 | ||
![]() |
275bbc81f0 | ||
![]() |
beafcf74ab | ||
![]() |
e47909bb3e | ||
![]() |
0b3b9c2257 | ||
![]() |
8fb7a7e4cd | ||
![]() |
c5ed148c52 | ||
![]() |
e774c710a8 | ||
![]() |
d237180a98 | ||
![]() |
d8b618f7c3 | ||
![]() |
e888a95bd1 | ||
![]() |
36c2404a46 | ||
![]() |
ba673beb82 | ||
![]() |
4b56701152 | ||
![]() |
59227116f3 | ||
![]() |
9b0975b2ac | ||
![]() |
3a39a5caa3 | ||
![]() |
93e270f379 | ||
![]() |
98c81fa2af | ||
![]() |
1bb32a05a9 | ||
![]() |
5dd4b77270 | ||
![]() |
737d1aac7c | ||
![]() |
886feae4ca | ||
![]() |
1dfe26f14f | ||
![]() |
d66fcd23df | ||
![]() |
bdfb47e999 | ||
![]() |
10300cc478 | ||
![]() |
ababa639b3 | ||
![]() |
9f6569d658 | ||
![]() |
24c22ebdc7 | ||
![]() |
6c365fffde | ||
![]() |
dbb80dd6c0 | ||
![]() |
624834de9c | ||
![]() |
d31995f878 | ||
![]() |
017b1cae26 | ||
![]() |
c09f15b0e9 | ||
![]() |
68284bed74 | ||
![]() |
9a44d668d6 | ||
![]() |
67e0197a7a | ||
![]() |
a5a8cfa17d | ||
![]() |
60c3e701e9 | ||
![]() |
b9b129dcf5 | ||
![]() |
d882ab236a | ||
![]() |
140cc0e486 | ||
![]() |
6ac7c0f893 | ||
![]() |
096d50617f | ||
![]() |
9dd8c0cc4f | ||
![]() |
de0fab86ec | ||
![]() |
bb36dd3893 | ||
![]() |
ada837ee95 | ||
![]() |
67e73173f6 | ||
![]() |
4b63829eef | ||
![]() |
029411d3fa | ||
![]() |
6ba033f934 | ||
![]() |
3734fa948f | ||
![]() |
336742e335 | ||
![]() |
66ca424d3a | ||
![]() |
2da0a91a36 | ||
![]() |
fee1bde231 | ||
![]() |
4a94430bf0 | ||
![]() |
cc337f7b1e | ||
![]() |
d8a06777fe | ||
![]() |
9207eedbfb | ||
![]() |
c97b832648 | ||
![]() |
4ef629f79d | ||
![]() |
0b4e3c3db5 | ||
![]() |
f12cc523b4 | ||
![]() |
5c3c9d2ed1 | ||
![]() |
3ac3673326 | ||
![]() |
1a3940575e | ||
![]() |
16c8b1efab | ||
![]() |
0e789be09f | ||
![]() |
a948c7d69d | ||
![]() |
d8ec0103a9 | ||
![]() |
50161670ce | ||
![]() |
c1f612dce1 | ||
![]() |
6fb74482d7 | ||
![]() |
4b680ffa5f | ||
![]() |
c71c8d56ce | ||
![]() |
295ae7b4bc | ||
![]() |
839c884cef | ||
![]() |
13ffe7acfb | ||
![]() |
39a0c0d96e | ||
![]() |
a95a542148 | ||
![]() |
b3cb2ac3ee | ||
![]() |
759fe54132 | ||
![]() |
519a888e82 | ||
![]() |
4f1e4e7471 | ||
![]() |
7b8a32f630 | ||
![]() |
92d91a65bb | ||
![]() |
dab5289177 | ||
![]() |
a77cb1e579 | ||
![]() |
01bdda0ae6 | ||
![]() |
fbe35e6e6b | ||
![]() |
a3cd74e30b | ||
![]() |
dbd4781de1 | ||
![]() |
6d48316436 | ||
![]() |
cca6965cd1 | ||
![]() |
dd63ed7e69 | ||
![]() |
61e2283146 | ||
![]() |
97eb768748 | ||
![]() |
be8b5a8aeb | ||
![]() |
99ed39b26c | ||
![]() |
48a0eb90a7 | ||
![]() |
3c342077d6 | ||
![]() |
f1bef1e7e6 | ||
![]() |
da9749ecce | ||
![]() |
fa7be597d2 | ||
![]() |
53da418d68 | ||
![]() |
897ed7e381 | ||
![]() |
daf0939f09 | ||
![]() |
7b1d6ddcf6 | ||
![]() |
267e1dd0f8 | ||
![]() |
c9d0bfce54 | ||
![]() |
7f9e5e29a8 | ||
![]() |
d0f685183d | ||
![]() |
bed77bd356 | ||
![]() |
bc0e3b254b | ||
![]() |
47bf0ebb47 | ||
![]() |
0acb95bbd5 | ||
![]() |
8665f4a251 | ||
![]() |
3adacb8799 | ||
![]() |
76aa69b9ac | ||
![]() |
78116f1596 | ||
![]() |
36693b7d9d | ||
![]() |
8ce68f93ea | ||
![]() |
3512cb9599 | ||
![]() |
ea164a2030 | ||
![]() |
929ba70ef8 | ||
![]() |
5b2113c43d | ||
![]() |
6df2c0bab5 | ||
![]() |
1c5193aa4d | ||
![]() |
bd55fe868d | ||
![]() |
87a2465a25 | ||
![]() |
5f839ad3ee | ||
![]() |
1663d8dfa9 | ||
![]() |
08eafc54e6 | ||
![]() |
fe1d8b137e | ||
![]() |
39c0826f3c | ||
![]() |
bf63b0993d | ||
![]() |
f91a1363cb | ||
![]() |
a2c9aa7662 | ||
![]() |
d135da6c1d | ||
![]() |
d27051f04d | ||
![]() |
b28fa2a1ad | ||
![]() |
77a91f5a8f | ||
![]() |
dcc7ee98b3 | ||
![]() |
30edb2a44f | ||
![]() |
f63332a7aa | ||
![]() |
86c37ce192 | ||
![]() |
93e6c9e5a0 | ||
![]() |
92e1fa4d3a | ||
![]() |
bf7d292884 | ||
![]() |
add8db0186 | ||
![]() |
3e62c6ae2f | ||
![]() |
cd4aa8ccd6 | ||
![]() |
937dbdc71f | ||
![]() |
66a7b508b2 | ||
![]() |
a5493f7947 | ||
![]() |
979c4907da | ||
![]() |
b8f6fdeb2b | ||
![]() |
067376cb3b | ||
![]() |
bdbe9255a6 | ||
![]() |
c460e1bbbe | ||
![]() |
7e2b72fa5e | ||
![]() |
6ee6a8a74f | ||
![]() |
80984c94a1 | ||
![]() |
1757b66467 | ||
![]() |
8aa25af014 | ||
![]() |
5a0e47be48 | ||
![]() |
756a866ffd | ||
![]() |
29305be23b | ||
![]() |
8253cfd21d | ||
![]() |
165a00896e | ||
![]() |
2149ea1306 | ||
![]() |
90547da007 | ||
![]() |
9ec4881d8d | ||
![]() |
487593af38 | ||
![]() |
4e8f878d83 | ||
![]() |
af6544c64d | ||
![]() |
09e1f53b3e | ||
![]() |
1c4f191f42 | ||
![]() |
a37bd824d5 | ||
![]() |
2c79173d20 | ||
![]() |
eb45b89557 | ||
![]() |
bf8c345341 | ||
![]() |
ef46280716 | ||
![]() |
2453e1284f | ||
![]() |
95bcb272e0 | ||
![]() |
e0e61b5262 | ||
![]() |
3ddef56167 | ||
![]() |
f8e6fb81d6 | ||
![]() |
683ec87adf | ||
![]() |
23edbe5ce7 | ||
![]() |
6ff32a51e3 | ||
![]() |
4cbac3a864 | ||
![]() |
94a99b5bec | ||
![]() |
810bf06e16 | ||
![]() |
1254667b2c | ||
![]() |
053eb8a0fd | ||
![]() |
82ef380256 | ||
![]() |
44449d8e72 | ||
![]() |
6c3a0890c7 | ||
![]() |
8c0def7c79 | ||
![]() |
de77751779 | ||
![]() |
cdf809926b | ||
![]() |
d40341f1ad | ||
![]() |
4a94fb91d7 | ||
![]() |
24ea9ca947 | ||
![]() |
98eb9bf2bd | ||
![]() |
1eb30cf3ab | ||
![]() |
6fd7c0ff8e | ||
![]() |
263e81cb2c | ||
![]() |
92ebf37d86 | ||
![]() |
a10e406131 | ||
![]() |
21095e80a7 | ||
![]() |
55ae43ed03 | ||
![]() |
9cc934a972 | ||
![]() |
cdfec7ebb4 | ||
![]() |
59ad69b637 | ||
![]() |
ca6b759607 | ||
![]() |
f9d857211f | ||
![]() |
01ad8661d6 | ||
![]() |
d21b8166f0 | ||
![]() |
63582bb489 | ||
![]() |
c19f2de3a8 | ||
![]() |
d2e7b61eb2 | ||
![]() |
13a448ebfe | ||
![]() |
bad2e1f9c4 | ||
![]() |
8edac51401 | ||
![]() |
f34ba9bf96 | ||
![]() |
82aea946a2 | ||
![]() |
a0665dc431 | ||
![]() |
e32d6cdecd | ||
![]() |
23b43319a8 | ||
![]() |
e7a7a18c43 | ||
![]() |
8e5abcf5c2 | ||
![]() |
e08e8641cb | ||
![]() |
3e8f3cfb49 | ||
![]() |
1eaaa5c6d3 | ||
![]() |
1cc776d332 | ||
![]() |
4009ae7d77 | ||
![]() |
07506faa3a | ||
![]() |
4d787ec93c | ||
![]() |
188413a531 | ||
![]() |
ad55c9cc19 | ||
![]() |
9b3ac49298 | ||
![]() |
4306b0caba | ||
![]() |
ebd1baa42c | ||
![]() |
6861bbed79 | ||
![]() |
25f66e6ac0 | ||
![]() |
838519e89f | ||
![]() |
be4641b8f3 | ||
![]() |
e861cab727 | ||
![]() |
f8f87ec091 | ||
![]() |
c0f1996478 | ||
![]() |
106746ce58 | ||
![]() |
62773fa88a | ||
![]() |
28a8ed62f3 | ||
![]() |
110751e992 | ||
![]() |
0d447c9d50 | ||
![]() |
827d6d1d2d | ||
![]() |
a64972fe38 | ||
![]() |
09bdc81aeb | ||
![]() |
c057de3a3c | ||
![]() |
1c4aff3ee1 | ||
![]() |
6bfed5c98c | ||
![]() |
f01231277b | ||
![]() |
94534f714c | ||
![]() |
1f9c06e606 | ||
![]() |
711c448242 | ||
![]() |
0c6a640e50 | ||
![]() |
11d9a71e5d | ||
![]() |
eed842fff1 | ||
![]() |
49fafcc68a | ||
![]() |
c46cccc3cd | ||
![]() |
dd714cc95e | ||
![]() |
f9ce8fa368 | ||
![]() |
4fc872a4cb | ||
![]() |
c8556f69e7 | ||
![]() |
0b3f660626 | ||
![]() |
7fa359764d | ||
![]() |
2bc642ae6f | ||
![]() |
5228aa5e5c | ||
![]() |
d9c61a37bb | ||
![]() |
5f662988ff | ||
![]() |
87c9c0c3b1 | ||
![]() |
0ede15dcbf | ||
![]() |
8ceecec5b8 | ||
![]() |
28ff138370 | ||
![]() |
e8acb48b1e | ||
![]() |
b588bd6e4f | ||
![]() |
d9fd2c28b0 | ||
![]() |
0a02ed2a39 | ||
![]() |
b13e1b3d44 | ||
![]() |
98732cb033 | ||
![]() |
eaa4a43458 | ||
![]() |
6af6b73c89 | ||
![]() |
990987ac92 | ||
![]() |
fd8f5b9ff0 | ||
![]() |
062b61affb | ||
![]() |
311aa74dd3 | ||
![]() |
0704c3ccb9 | ||
![]() |
d375dca1f1 | ||
![]() |
5f04a6239e | ||
![]() |
6f9c99ac6c | ||
![]() |
3c50b00a9a | ||
![]() |
ce8893ef6b | ||
![]() |
5fb7bb50e0 | ||
![]() |
15bd5bf6f6 | ||
![]() |
b35c1d852e | ||
![]() |
93ec127245 | ||
![]() |
42613dbcf8 | ||
![]() |
a9ec5f5c38 | ||
![]() |
10b04f41df | ||
![]() |
8a16504988 | ||
![]() |
703e51d500 | ||
![]() |
f97d6b552b | ||
![]() |
f592c64c6a | ||
![]() |
5d5355bc41 | ||
![]() |
f02c14d327 | ||
![]() |
ca4f971eb4 | ||
![]() |
e43bf3b05a | ||
![]() |
38e7dcfd12 | ||
![]() |
6ccb4b726a | ||
![]() |
ecf167e889 | ||
![]() |
dce819f57b | ||
![]() |
fe7328b92e | ||
![]() |
46fa9e6b82 | ||
![]() |
76712439ee | ||
![]() |
5a2830a654 | ||
![]() |
b34ca9a521 | ||
![]() |
7fc4a65868 | ||
![]() |
85899a59c0 | ||
![]() |
73214be565 | ||
![]() |
175a87f948 | ||
![]() |
201aab9f73 | ||
![]() |
0581d614f6 | ||
![]() |
9622a11b2e | ||
![]() |
f17c5bc334 | ||
![]() |
ed9f40fc4c | ||
![]() |
31a58a21c6 | ||
![]() |
061ece55f3 | ||
![]() |
391f278ee5 | ||
![]() |
004b323fd4 | ||
![]() |
088cfed794 | ||
![]() |
908f649ea7 | ||
![]() |
c214adcdf0 | ||
![]() |
097ba07f20 | ||
![]() |
0c04373b79 | ||
![]() |
dd8f1800df | ||
![]() |
81b918c392 | ||
![]() |
43038564fe | ||
![]() |
bcd77de328 | ||
![]() |
0d90d6586e | ||
![]() |
2324bccbe7 | ||
![]() |
0cb07f511a | ||
![]() |
e22fb444d3 | ||
![]() |
9a09c1b027 | ||
![]() |
a94968b6bb | ||
![]() |
3ac05f1fa9 | ||
![]() |
ad3effa7d1 | ||
![]() |
d094c0d2b3 | ||
![]() |
157e7f9f78 | ||
![]() |
5816342bed | ||
![]() |
22491afa58 | ||
![]() |
a023b71ce0 | ||
![]() |
392848c885 | ||
![]() |
a815661de1 | ||
![]() |
ff6261ccc8 | ||
![]() |
7e68368d0a | ||
![]() |
f21c8d895f | ||
![]() |
120e17fa1e | ||
![]() |
42e6ac4f6d | ||
![]() |
7a77a3d7ce | ||
![]() |
bf9b35d670 | ||
![]() |
e0a14cdeea | ||
![]() |
099a3f4f90 | ||
![]() |
4d41f82794 | ||
![]() |
1d24bfb99d | ||
![]() |
47b809c7b7 | ||
![]() |
f3f6cb03e6 | ||
![]() |
a301d51fb2 | ||
![]() |
d6703b20d3 | ||
![]() |
8c4b076746 | ||
![]() |
356e09091d | ||
![]() |
d4c9841e44 | ||
![]() |
bea13d039f | ||
![]() |
1f8fd52103 | ||
![]() |
94506c3c90 | ||
![]() |
515771553f | ||
![]() |
e204812d2b | ||
![]() |
ca703cb858 | ||
![]() |
b018d4a97d | ||
![]() |
146768ff8a | ||
![]() |
ea7473ed67 | ||
![]() |
0e8393766f | ||
![]() |
7d2536c503 | ||
![]() |
f9cbf1b30c | ||
![]() |
d66d87d271 | ||
![]() |
5a8fa6cf38 | ||
![]() |
76340035db | ||
![]() |
0a26e68d0c | ||
![]() |
14127b910f | ||
![]() |
ba4d081021 | ||
![]() |
18d65d513e | ||
![]() |
3cf9e2d9f6 | ||
![]() |
080842e44c | ||
![]() |
49d534e779 | ||
![]() |
5a0ef149a5 | ||
![]() |
d2eb0e1fde | ||
![]() |
2d90ffcbf0 | ||
![]() |
5580c3fda0 | ||
![]() |
10d26bf734 | ||
![]() |
57ef175050 | ||
![]() |
275c86a0a9 | ||
![]() |
5fa6202111 | ||
![]() |
1e001469f6 | ||
![]() |
4251389c12 | ||
![]() |
6ff2ce1895 | ||
![]() |
1abc953cad | ||
![]() |
c696a3b789 | ||
![]() |
5986646af4 | ||
![]() |
5674c1d82f | ||
![]() |
c1c0a281cf | ||
![]() |
84d4a1ce34 | ||
![]() |
8a4d72e3b1 | ||
![]() |
a7b5e4323e | ||
![]() |
409f1bb644 | ||
![]() |
1d5821abca | ||
![]() |
b3eca73e48 | ||
![]() |
7694326a4e | ||
![]() |
9c026bc442 | ||
![]() |
b812306bd7 | ||
![]() |
0e667dfe36 | ||
![]() |
9037421a85 | ||
![]() |
1a9c6deb0d | ||
![]() |
f08d2716ae | ||
![]() |
f37c0e0548 | ||
![]() |
be2c3217dc | ||
![]() |
937d15d7e1 | ||
![]() |
35ff3afa12 | ||
![]() |
536d702d96 | ||
![]() |
420070a1ee | ||
![]() |
cd4a13ca55 | ||
![]() |
9d0701a62b | ||
![]() |
7c9a198c6d | ||
![]() |
065577c9ca | ||
![]() |
8533f853c8 | ||
![]() |
2f60395ba9 | ||
![]() |
906cecf087 | ||
![]() |
cb1b917aa6 | ||
![]() |
006d511d47 | ||
![]() |
f6270d9cfc | ||
![]() |
72f1c358d9 | ||
![]() |
3cbadb1bd2 | ||
![]() |
5d058c29a2 | ||
![]() |
4964470e9c | ||
![]() |
f4dfe7868b | ||
![]() |
82e9792b4d | ||
![]() |
af41a41046 | ||
![]() |
59e5eb9a1c | ||
![]() |
a0637a6ff8 | ||
![]() |
5497697cf2 | ||
![]() |
b07682e43c | ||
![]() |
0bc572787a | ||
![]() |
15fc4a8ae4 | ||
![]() |
8bf7243549 | ||
![]() |
66395d5fe5 | ||
![]() |
f9509d2b38 | ||
![]() |
7c50b8185d | ||
![]() |
350a27575f | ||
![]() |
494511e099 | ||
![]() |
11ac8f8006 | ||
![]() |
bcac851677 | ||
![]() |
c294130080 | ||
![]() |
c0f19dd963 | ||
![]() |
e5a07da0c9 | ||
![]() |
1ad3a96643 | ||
![]() |
ac6d893758 | ||
![]() |
9f2bdca9ad | ||
![]() |
c5046f7809 | ||
![]() |
f7897bbd64 | ||
![]() |
6442625a9d | ||
![]() |
5d079aacd6 | ||
![]() |
0e7297873c | ||
![]() |
dfb94d8917 | ||
![]() |
ed445d0ab8 | ||
![]() |
dddc1906c2 | ||
![]() |
144454b8c3 | ||
![]() |
8ae8fa7ec9 | ||
![]() |
1ff1b82fc7 | ||
![]() |
5d590bc2cf | ||
![]() |
866f1e70a4 | ||
![]() |
e273148a89 | ||
![]() |
c5f8d823ce | ||
![]() |
25e887b457 | ||
![]() |
aac5ac6057 | ||
![]() |
36a1eaedcf | ||
![]() |
bb9f534259 | ||
![]() |
cf9e5ae5a0 | ||
![]() |
d2db25c7dd | ||
![]() |
2542ddd30a | ||
![]() |
260d919f80 | ||
![]() |
fa8284d360 | ||
![]() |
fb7bed2ea0 | ||
![]() |
84b2c74057 | ||
![]() |
a14cb13194 | ||
![]() |
78fce90178 | ||
![]() |
a158e893e0 | ||
![]() |
117bc67a4c | ||
![]() |
9930473390 | ||
![]() |
b41ef73ecb | ||
![]() |
52b6574240 | ||
![]() |
0d857d3e6a | ||
![]() |
3ba3fbf4a5 | ||
![]() |
c3e7fcc153 | ||
![]() |
2c00cd489e | ||
![]() |
df52f3f0e1 | ||
![]() |
11e8e56e05 | ||
![]() |
a5ecbd547c | ||
![]() |
866912d3f7 | ||
![]() |
56e550f136 | ||
![]() |
1e179adcf2 | ||
![]() |
c5fe7ea0ea | ||
![]() |
fdda0cc9cc | ||
![]() |
f9dfc64c6f | ||
![]() |
f41494b7cc | ||
![]() |
821d9abc56 | ||
![]() |
f5b55d5eb3 | ||
![]() |
cdb1b1df15 | ||
![]() |
8e6b41e637 | ||
![]() |
1f7cc5f5ec | ||
![]() |
1a0c3a49b9 | ||
![]() |
c4e2e9c4f0 | ||
![]() |
7df973648c | ||
![]() |
25aea140be | ||
![]() |
8d2cf0cf38 | ||
![]() |
d2bbfe1282 | ||
![]() |
6d72391ee1 | ||
![]() |
a53e02b51b | ||
![]() |
401e334c28 | ||
![]() |
4c10ce6f40 | ||
![]() |
fed6a4689f | ||
![]() |
6dc5a9efde | ||
![]() |
cb1e5a2412 | ||
![]() |
f47a012c62 | ||
![]() |
e6e22dc0bf | ||
![]() |
d8589113c3 | ||
![]() |
188e503070 | ||
![]() |
e4f7ac6236 | ||
![]() |
7178943223 | ||
![]() |
27c76e746a | ||
![]() |
de47776ea5 | ||
![]() |
7e56b595a0 | ||
![]() |
d15a9a4359 | ||
![]() |
c4ff3f731b | ||
![]() |
886399284b | ||
![]() |
bf73e09918 | ||
![]() |
fef36e677d | ||
![]() |
441fdc35b2 | ||
![]() |
801c73ef94 | ||
![]() |
3e56185a39 | ||
![]() |
caf85fe61d | ||
![]() |
3112682923 | ||
![]() |
edb30af441 | ||
![]() |
b5a6bb74ce | ||
![]() |
8236a9529f | ||
![]() |
c50d0646ab | ||
![]() |
1484a9c0ee | ||
![]() |
abe02c3843 | ||
![]() |
8e9e738bb8 | ||
![]() |
bd97a0dfe3 | ||
![]() |
9a59cba7f3 | ||
![]() |
07021dbd65 | ||
![]() |
8ee8421af7 | ||
![]() |
f983446355 | ||
![]() |
6650d32055 | ||
![]() |
0badff98c6 | ||
![]() |
1630bf5de7 | ||
![]() |
2b2820018c | ||
![]() |
8540343d7f | ||
![]() |
e52b347b18 | ||
![]() |
67e0ccf677 | ||
![]() |
ba6bcf86ca | ||
![]() |
6952d2420f | ||
![]() |
a85d7af9e7 | ||
![]() |
39e63aee0c | ||
![]() |
9176994947 | ||
![]() |
d389b55f40 | ||
![]() |
0ccff9fc54 | ||
![]() |
a8836ca7b6 | ||
![]() |
f5d04a970f | ||
![]() |
7aec98dafd | ||
![]() |
773564d4f5 | ||
![]() |
3e2edc1a2d | ||
![]() |
6a12a24d73 | ||
![]() |
9cfc9b9baf | ||
![]() |
1739647768 | ||
![]() |
92b67ead83 | ||
![]() |
ee9525cc00 | ||
![]() |
571bfaf5d7 | ||
![]() |
a3475607b2 | ||
![]() |
f0a653d010 | ||
![]() |
eecdf66013 | ||
![]() |
f99db05a4a | ||
![]() |
635731421f | ||
![]() |
44743df7d6 | ||
![]() |
33617694cc | ||
![]() |
ed445d20b9 | ||
![]() |
66c2fe091b | ||
![]() |
8c80f47a35 | ||
![]() |
e37025c1c7 | ||
![]() |
0aabde081b | ||
![]() |
a1c9d53474 | ||
![]() |
094996ad0c | ||
![]() |
ce359a7689 | ||
![]() |
ee599160b3 | ||
![]() |
dd076f7a13 | ||
![]() |
3021d38b6f | ||
![]() |
bfcabeaf26 | ||
![]() |
c31e0336dc | ||
![]() |
a1e42cac7a | ||
![]() |
14a3e5b771 | ||
![]() |
5901c543da | ||
![]() |
456b80e6ae | ||
![]() |
e5644ae011 | ||
![]() |
41c794c733 | ||
![]() |
a481448d46 | ||
![]() |
2bd7ce618a | ||
![]() |
da1ac4f1e9 | ||
![]() |
f0cb638106 | ||
![]() |
dad2396d01 | ||
![]() |
91e4d8b663 | ||
![]() |
e35496133e | ||
![]() |
3be808ae1e | ||
![]() |
c5772916a1 | ||
![]() |
8cd63b80b1 | ||
![]() |
c087654386 | ||
![]() |
60b9e65c78 | ||
![]() |
79b304a5d2 | ||
![]() |
bb9fd126e5 | ||
![]() |
bff2d5c26c | ||
![]() |
46d9ac8380 | ||
![]() |
5da3ca4bb1 | ||
![]() |
2c99fdc092 | ||
![]() |
31a075fb13 | ||
![]() |
1d132d7a1e | ||
![]() |
3b6f88cfa7 | ||
![]() |
b927763d8d | ||
![]() |
d00e1cb6a5 | ||
![]() |
adf7474edb | ||
![]() |
041d663cb8 | ||
![]() |
37f611a8d3 | ||
![]() |
be99329efa | ||
![]() |
327cb70bb8 | ||
![]() |
be2b5a4c3a | ||
![]() |
d1eda9dd73 | ||
![]() |
b902cb5a13 | ||
![]() |
1184ee4a59 | ||
![]() |
2cf898afcc | ||
![]() |
38fc0bd889 | ||
![]() |
df53e19eda | ||
![]() |
7f79b26341 | ||
![]() |
63391717e7 | ||
![]() |
554629f37a | ||
![]() |
791c3cd955 | ||
![]() |
6e53c93271 | ||
![]() |
7097315079 | ||
![]() |
d50758197e | ||
![]() |
9ff35d5a5a | ||
![]() |
00f7bdbfaa | ||
![]() |
e682d3461f | ||
![]() |
71898d0c8c | ||
![]() |
416ead5311 | ||
![]() |
c7882450ac | ||
![]() |
1eea5b8a58 | ||
![]() |
7341337b5f | ||
![]() |
964d87ae10 | ||
![]() |
cb02c723e0 | ||
![]() |
4c1b7add39 | ||
![]() |
252aa1410b | ||
![]() |
64693eaca2 | ||
![]() |
c39a1596d5 | ||
![]() |
46184188e4 | ||
![]() |
d9f4f424fd | ||
![]() |
6b3f18cb5d | ||
![]() |
75c22b6a6f | ||
![]() |
19e7fdfdb0 | ||
![]() |
8ba14ef113 | ||
![]() |
50025971d8 | ||
![]() |
bcbba04f27 | ||
![]() |
74ba8877d4 | ||
![]() |
ec91d74456 | ||
![]() |
cb014cf255 | ||
![]() |
af08b73280 | ||
![]() |
e86d568536 | ||
![]() |
9f7eb36a1f | ||
![]() |
0fcbfa996f | ||
![]() |
2ab5e5d267 | ||
![]() |
5b7bd6a52f | ||
![]() |
ebb8ec954d | ||
![]() |
dd856a9116 | ||
![]() |
5e38bb7a32 | ||
![]() |
f446e42317 | ||
![]() |
67f67a02f8 | ||
![]() |
a9aa5ad229 | ||
![]() |
f504c27972 | ||
![]() |
9b3f92e265 | ||
![]() |
4efb747389 | ||
![]() |
347440019e | ||
![]() |
39891ffe60 | ||
![]() |
9bbbb2cd3c | ||
![]() |
2a171fb08c | ||
![]() |
9f6412a976 | ||
![]() |
8a6a13db0e | ||
![]() |
fbec61662b | ||
![]() |
9d7f0e77f1 | ||
![]() |
983607e683 | ||
![]() |
c3cbdd0eb9 | ||
![]() |
b56fa7b406 | ||
![]() |
dabc38dbff | ||
![]() |
e6bba49bcd | ||
![]() |
23a1046a8f | ||
![]() |
7c6b517672 | ||
![]() |
f6188949f3 | ||
![]() |
ff1ea46c46 | ||
![]() |
e8bc07d40f | ||
![]() |
fa717699f5 | ||
![]() |
8dfb8ebe5c | ||
![]() |
7b6cac558d | ||
![]() |
f13f4a4851 | ||
![]() |
11245dbb82 | ||
![]() |
805bed092e | ||
![]() |
b38694fbcd | ||
![]() |
ac7dc03603 | ||
![]() |
253a5e3e4b | ||
![]() |
b8131cee2e | ||
![]() |
c8178ab915 | ||
![]() |
577ae6923a | ||
![]() |
fcaec2c3f4 | ||
![]() |
78f4b28697 | ||
![]() |
acd32b500c | ||
![]() |
168d0f11ab | ||
![]() |
6da8b69ff8 | ||
![]() |
3fa460a42a | ||
![]() |
021e7ce49b | ||
![]() |
2d093e9692 | ||
![]() |
30a244de7a | ||
![]() |
195398713b | ||
![]() |
3d1e57766a | ||
![]() |
dd08381167 | ||
![]() |
c096cc23df | ||
![]() |
d8d000f279 | ||
![]() |
5dd91db5c0 | ||
![]() |
c97be4d0d1 | ||
![]() |
fa53ec40d6 | ||
![]() |
413a4cd7bd | ||
![]() |
c22bbc5b91 | ||
![]() |
6f45e376da | ||
![]() |
5e6a38769d | ||
![]() |
f5797e3799 | ||
![]() |
bbbbd0810a | ||
![]() |
053e2a52b8 | ||
![]() |
99eb466223 | ||
![]() |
2dec36f210 | ||
![]() |
8b46c8bf20 | ||
![]() |
77d83bffee | ||
![]() |
666e8e133e | ||
![]() |
959898e0fc | ||
![]() |
d41b9beb71 | ||
![]() |
00a037c786 | ||
![]() |
d8b51b4f2c | ||
![]() |
4d003f51c3 | ||
![]() |
e01512e469 | ||
![]() |
5836a85340 | ||
![]() |
4e15556eeb | ||
![]() |
0956dbb578 | ||
![]() |
0c0ff855b1 | ||
![]() |
017ba509a6 | ||
![]() |
9d9b5af97f | ||
![]() |
dd5e5323f1 | ||
![]() |
cee7017d20 | ||
![]() |
c9311ea3c9 | ||
![]() |
92f08be416 | ||
![]() |
4478f64002 | ||
![]() |
b56e22d4ee | ||
![]() |
d6ee10a543 | ||
![]() |
217165208b | ||
![]() |
2c664efb3c | ||
![]() |
15a1a83729 | ||
![]() |
c87415023c | ||
![]() |
86fddf2ec1 | ||
![]() |
1eb8d0fa1c | ||
![]() |
6df77ef94b | ||
![]() |
bff66dbbd3 | ||
![]() |
646f457637 | ||
![]() |
b37d9179e6 | ||
![]() |
eff9d568a2 | ||
![]() |
11793f04c1 | ||
![]() |
79e8a694ad | ||
![]() |
4a202eca59 | ||
![]() |
70fcca824b | ||
![]() |
f2e0190b68 | ||
![]() |
20205d7ff4 | ||
![]() |
3b195f61da | ||
![]() |
99a40ae49f | ||
![]() |
ba4ed5a1bb | ||
![]() |
ac42ff5d73 | ||
![]() |
4bb3d69631 | ||
![]() |
e6aac6a77f | ||
![]() |
bb4e8e5717 | ||
![]() |
c963e280ca | ||
![]() |
ea1ce6a263 | ||
![]() |
412acf943d | ||
![]() |
1984989564 | ||
![]() |
e1988cd6fc | ||
![]() |
6269ce36b3 | ||
![]() |
9ab81eb444 | ||
![]() |
b75ed5b8f1 | ||
![]() |
fbd9502474 | ||
![]() |
b32c4a8fbb | ||
![]() |
3755f598a2 | ||
![]() |
af7a9ff591 | ||
![]() |
55376ea7f0 | ||
![]() |
3a2843b9fa | ||
![]() |
6dbfce0095 | ||
![]() |
1613b3c0df | ||
![]() |
00ee2b4478 | ||
![]() |
9a0cb59830 | ||
![]() |
54c4fb5f56 | ||
![]() |
c3bf1dde7e | ||
![]() |
963bba63d0 | ||
![]() |
eac930ad7f | ||
![]() |
4c175a3ed9 | ||
![]() |
ec39ec69bb | ||
![]() |
3107785947 | ||
![]() |
fe130b62c8 | ||
![]() |
f0363ac221 | ||
![]() |
75936fcb9c | ||
![]() |
ffbb894dd6 | ||
![]() |
7271a64ac2 | ||
![]() |
ee65f60222 | ||
![]() |
c41e3cbf93 | ||
![]() |
599076d6f4 | ||
![]() |
06170592bd | ||
![]() |
14111188c0 | ||
![]() |
d185f230b9 | ||
![]() |
079ba2a529 | ||
![]() |
d99429463b | ||
![]() |
563de82707 | ||
![]() |
8c0e96e6e6 | ||
![]() |
550858092c | ||
![]() |
ae6add1e77 | ||
![]() |
927943e07a | ||
![]() |
4cfb1c573e | ||
![]() |
a36b516070 | ||
![]() |
cb0ae29308 | ||
![]() |
6ee452aef3 | ||
![]() |
bce274155c | ||
![]() |
019aff88ca | ||
![]() |
c87a2ca335 | ||
![]() |
4e650ec1ba | ||
![]() |
7ec911c4df | ||
![]() |
605aaf955c | ||
![]() |
e78a3f7939 | ||
![]() |
54401bc0a5 | ||
![]() |
a02ef0dbc8 | ||
![]() |
34c464e8d0 | ||
![]() |
32570c59c8 | ||
![]() |
4721f8ef5f | ||
![]() |
3c458353f0 | ||
![]() |
59e3c4874d | ||
![]() |
4693f436a5 | ||
![]() |
b4dfd08bc4 | ||
![]() |
3cda93d001 | ||
![]() |
e705ca83b2 | ||
![]() |
f90ed9e9db | ||
![]() |
808d93d767 | ||
![]() |
0d795aad16 | ||
![]() |
3e8bc98f23 | ||
![]() |
546d0b25b0 | ||
![]() |
1f04723d8d | ||
![]() |
cd78e2fc43 | ||
![]() |
f6850c36fc | ||
![]() |
8f96256e86 | ||
![]() |
4404fb72bd | ||
![]() |
8ae3b430c8 | ||
![]() |
e6bc1f0730 | ||
![]() |
747f7a1b04 | ||
![]() |
01e7c45664 | ||
![]() |
65c0d49c3b | ||
![]() |
cba9e5845d | ||
![]() |
c6e5011a98 | ||
![]() |
229ad8be83 | ||
![]() |
d58b2d1b32 | ||
![]() |
b69f2856bf | ||
![]() |
76a59338eb | ||
![]() |
4003e93999 | ||
![]() |
fe9ae0d8bd | ||
![]() |
0326e58c8a | ||
![]() |
c043142b86 | ||
![]() |
c28edb1117 | ||
![]() |
204bea8947 | ||
![]() |
39e65c8586 | ||
![]() |
e54031e318 | ||
![]() |
24fbc366a6 | ||
![]() |
c104e66964 | ||
![]() |
00df42ba39 | ||
![]() |
c4fb4eb61b | ||
![]() |
a9495aceb4 | ||
![]() |
0999297e58 | ||
![]() |
62ae2a3bd5 | ||
![]() |
213cc14494 | ||
![]() |
59ebb94d24 | ||
![]() |
a11b32dae5 | ||
![]() |
0177facbf0 | ||
![]() |
b8d252273d | ||
![]() |
b942569ce0 | ||
![]() |
275b9ce718 | ||
![]() |
58d0dbb542 | ||
![]() |
f84a01d840 | ||
![]() |
6ee03460d6 | ||
![]() |
d01fb914a9 | ||
![]() |
2182bc3af2 | ||
![]() |
d9b077154e | ||
![]() |
7e6c106869 | ||
![]() |
c3e37ef9a0 | ||
![]() |
8bbbaae290 | ||
![]() |
79de27544c | ||
![]() |
2cbf53ad7b | ||
![]() |
c52607b465 | ||
![]() |
087566072d | ||
![]() |
6b814afd39 | ||
![]() |
ea8aa6b07d | ||
![]() |
1b0f731e30 | ||
![]() |
1ebde4a880 | ||
![]() |
e53bd477b4 | ||
![]() |
3f9287c36b | ||
![]() |
b2b940fc32 | ||
![]() |
7d9e170512 | ||
![]() |
6ab92abe80 | ||
![]() |
e30db943db | ||
![]() |
e2b4a24a35 | ||
![]() |
20e3291eb9 | ||
![]() |
ebfa2fb1d0 | ||
![]() |
ae8219dc97 | ||
![]() |
64ea02983b | ||
![]() |
f1b6ae8784 | ||
![]() |
8b9b65d3f1 | ||
![]() |
e82368ec85 | ||
![]() |
d1bee8fe61 | ||
![]() |
07704b8511 | ||
![]() |
6a44d66fed | ||
![]() |
10895514c6 | ||
![]() |
1290f18ed4 | ||
![]() |
49e634a62f | ||
![]() |
8754b54d81 | ||
![]() |
c191a7cfdb | ||
![]() |
6eb49991a4 | ||
![]() |
1dd59375f6 | ||
![]() |
c3f0f30910 | ||
![]() |
10c033e580 | ||
![]() |
48a07d531c | ||
![]() |
80582a128a | ||
![]() |
db494de809 | ||
![]() |
68d58212a9 | ||
![]() |
48a6dabc5b | ||
![]() |
0ae0047246 | ||
![]() |
49dec1577e | ||
![]() |
4e30bf705c | ||
![]() |
4e9a91d03f | ||
![]() |
2f8c9d4f93 | ||
![]() |
0f29fd3e10 | ||
![]() |
58f786f6d0 | ||
![]() |
e2b1ef053f | ||
![]() |
0bbca596a9 | ||
![]() |
09014e3390 | ||
![]() |
c634f6067a | ||
![]() |
255cf6b305 | ||
![]() |
07bc9f6477 | ||
![]() |
464da23d4e | ||
![]() |
a2c85a0ac2 | ||
![]() |
f837369ef0 | ||
![]() |
153b3fbfc8 | ||
![]() |
c7739a7760 | ||
![]() |
41fcf58b80 | ||
![]() |
13e7af7762 | ||
![]() |
c38f23400c | ||
![]() |
62b449e52c | ||
![]() |
85ae66d276 | ||
![]() |
e15ae6bea3 | ||
![]() |
bbf8a49ac8 | ||
![]() |
b95fc7e782 | ||
![]() |
7cd4f78767 | ||
![]() |
49882112ac | ||
![]() |
c957c7a515 | ||
![]() |
045d96cdd1 | ||
![]() |
a218f4adc3 | ||
![]() |
9ba58233ec | ||
![]() |
4aedf662e9 | ||
![]() |
59e486848c | ||
![]() |
4cef435089 | ||
![]() |
d128ba544f | ||
![]() |
372a827ecd | ||
![]() |
0eec6447e4 | ||
![]() |
13d72ac833 | ||
![]() |
abf3da2fa1 | ||
![]() |
df8269e772 | ||
![]() |
19535b48ab | ||
![]() |
da68c48723 | ||
![]() |
44523168d7 | ||
![]() |
2b50f65c49 | ||
![]() |
14c2778558 | ||
![]() |
7878d2804f | ||
![]() |
cef56bd7ef | ||
![]() |
c02a3371d0 | ||
![]() |
c2c48bbc9c | ||
![]() |
131fdf6898 | ||
![]() |
a2e4de2d0d | ||
![]() |
7d3d693fe8 | ||
![]() |
94df3e931a | ||
![]() |
2e225dfc3a | ||
![]() |
409d7b3652 | ||
![]() |
c658dc0ffc | ||
![]() |
13e4cd4a49 | ||
![]() |
609d410e6a | ||
![]() |
04860ae1d2 | ||
![]() |
a0a90f03a8 | ||
![]() |
be3a883c51 | ||
![]() |
0fde5c21b7 | ||
![]() |
c4cc9f8489 | ||
![]() |
ff7bc13058 | ||
![]() |
ddea61148f | ||
![]() |
88ad7e98e0 | ||
![]() |
a83d38a5fe | ||
![]() |
a3b1a30d06 | ||
![]() |
f8b192bd94 | ||
![]() |
4c6ab3921a | ||
![]() |
1dc1fd421b | ||
![]() |
fed953023d | ||
![]() |
5a4cdaf348 | ||
![]() |
4d49cb2d18 | ||
![]() |
5759539e08 | ||
![]() |
74441d2771 | ||
![]() |
da0ebbe57c | ||
![]() |
d8d392990d | ||
![]() |
5db4a73d8e | ||
![]() |
829af75416 | ||
![]() |
acb0aeaa9a | ||
![]() |
dc7c909316 | ||
![]() |
a052e15319 | ||
![]() |
c306ebed49 | ||
![]() |
301701176a | ||
![]() |
2ab66f62fa | ||
![]() |
a50b299a82 | ||
![]() |
a6808a8fda | ||
![]() |
f5bd81e0d9 | ||
![]() |
c265c91ef2 | ||
![]() |
3184951625 | ||
![]() |
c6fa160c02 | ||
![]() |
e2eb986c7c | ||
![]() |
9219339762 | ||
![]() |
fac3d575c9 | ||
![]() |
db9257f9fa | ||
![]() |
bb21c87852 | ||
![]() |
f0df25f824 | ||
![]() |
7ac944c537 | ||
![]() |
7d3dd2dd6b | ||
![]() |
48538ef5d5 | ||
![]() |
acd3710469 | ||
![]() |
56e79de707 | ||
![]() |
781c3eed2f | ||
![]() |
d0d8de94dc | ||
![]() |
4974202bb4 | ||
![]() |
2dce115732 | ||
![]() |
ce1d4282db | ||
![]() |
befc730769 | ||
![]() |
3f7c6055d4 | ||
![]() |
5ed7efb01d | ||
![]() |
f0f924a0a2 | ||
![]() |
a19a069b21 | ||
![]() |
a43bfdef1d | ||
![]() |
7994729742 | ||
![]() |
f24523e93b | ||
![]() |
98a8430f26 | ||
![]() |
273795b025 | ||
![]() |
f6c7ade579 | ||
![]() |
84a4fe7b03 | ||
![]() |
583ce7dc46 | ||
![]() |
81d7d2a70a | ||
![]() |
689372b572 | ||
![]() |
083be5d0a5 | ||
![]() |
4726dc96d4 | ||
![]() |
3308de95f0 | ||
![]() |
ea115e0481 | ||
![]() |
2fdde24024 | ||
![]() |
d21d6c2e4a | ||
![]() |
4c1863d318 | ||
![]() |
b8a00bfbfb | ||
![]() |
4cd6813d16 | ||
![]() |
21266e1c68 | ||
![]() |
201b8d9ebf | ||
![]() |
2440023dd7 | ||
![]() |
5f3e70f915 | ||
![]() |
5365439fd4 | ||
![]() |
9c28a4e8a0 | ||
![]() |
565203047c | ||
![]() |
b9795a2ae7 | ||
![]() |
3f1acdc9ec | ||
![]() |
f053e5ca38 | ||
![]() |
9873371413 | ||
![]() |
2a2af01d12 | ||
![]() |
4e4f8ee3a4 | ||
![]() |
b8fd921c81 | ||
![]() |
fcf91954ff | ||
![]() |
49708196ac | ||
![]() |
8c8a2eef21 | ||
![]() |
7790bb528c | ||
![]() |
5bd2d27488 | ||
![]() |
cd090ff000 | ||
![]() |
47985a589e | ||
![]() |
375d47ee3a | ||
![]() |
e3e68dad36 | ||
![]() |
40dbfab671 | ||
![]() |
46405d7738 | ||
![]() |
905ac20205 | ||
![]() |
6d65d6bcf6 | ||
![]() |
f4ab741445 | ||
![]() |
c1fa3d99f3 | ||
![]() |
fcc0eba7fb | ||
![]() |
d7da3de096 | ||
![]() |
0616bc7fec | ||
![]() |
dd478fe681 | ||
![]() |
c175a68a26 | ||
![]() |
98a86c7636 | ||
![]() |
4060705d87 | ||
![]() |
1c11229510 | ||
![]() |
7129868a56 | ||
![]() |
4a6e3e0f5a | ||
![]() |
b6a0a36d4e | ||
![]() |
3b7ae1639c | ||
![]() |
3fb7547d4d | ||
![]() |
10c0633af9 | ||
![]() |
3460f460d1 | ||
![]() |
bb70a0feb2 | ||
![]() |
44eb4e0c9e | ||
![]() |
97bbad7471 | ||
![]() |
df6370dd61 | ||
![]() |
ee8f4a5367 | ||
![]() |
a5135cf2c3 | ||
![]() |
41932b4501 | ||
![]() |
028d1c6148 | ||
![]() |
1efe418e05 | ||
![]() |
4251ee1229 | ||
![]() |
95a79130a2 | ||
![]() |
e2518ab4d7 | ||
![]() |
9d557f47b7 | ||
![]() |
c654d3283e | ||
![]() |
8e6b6269a7 | ||
![]() |
a2404e7fb8 | ||
![]() |
6321978f75 | ||
![]() |
448dd61684 | ||
![]() |
a3513b24ec | ||
![]() |
57905efcd3 | ||
![]() |
3052eca564 | ||
![]() |
120f4adf35 | ||
![]() |
b95dfe2b00 | ||
![]() |
f02f0eae59 | ||
![]() |
c5ebd53079 | ||
![]() |
963b9d9a83 | ||
![]() |
5bf5545394 | ||
![]() |
2659097010 | ||
![]() |
5f1470af9f | ||
![]() |
4ceff8cabf | ||
![]() |
b12f3e5aff | ||
![]() |
f0b57e2873 | ||
![]() |
f71baf3c73 | ||
![]() |
d9bba25f67 | ||
![]() |
eb1fe93a59 | ||
![]() |
825bce32b5 | ||
![]() |
36df9e0464 | ||
![]() |
46480c5624 | ||
![]() |
73fad671ed | ||
![]() |
0d9f2aee70 | ||
![]() |
805c717013 | ||
![]() |
9fcb1da06b | ||
![]() |
dcb6c9a133 | ||
![]() |
edcb4eca22 | ||
![]() |
de6ca56504 | ||
![]() |
fdd9fca5b3 | ||
![]() |
10805805fe | ||
![]() |
05288dad51 | ||
![]() |
053ff33ef9 | ||
![]() |
c97f1baa2b | ||
![]() |
0f4c50e83c | ||
![]() |
0a99c1c633 | ||
![]() |
d6ae47a0de | ||
![]() |
b258e6464d | ||
![]() |
86a95013b6 | ||
![]() |
636cba5d6b | ||
![]() |
74931071de | ||
![]() |
c92169cb20 | ||
![]() |
927813ab3b | ||
![]() |
060268747c | ||
![]() |
47c953209d | ||
![]() |
16df3eb995 | ||
![]() |
454fb30759 | ||
![]() |
4e157c2999 | ||
![]() |
d96fd518e7 | ||
![]() |
07fa1fa771 | ||
![]() |
404b3fcd03 | ||
![]() |
e1db5f3cac | ||
![]() |
4bb768f39c | ||
![]() |
730012edfd | ||
![]() |
352987db7e | ||
![]() |
92a6f231a9 | ||
![]() |
a44bf164e5 | ||
![]() |
5cc8cfb209 | ||
![]() |
f99b7d8b78 | ||
![]() |
e8fd97e355 | ||
![]() |
5e64caa225 | ||
![]() |
0672e1a1ea | ||
![]() |
2deab9e0c2 | ||
![]() |
ebe4ed99b5 | ||
![]() |
dce51b02c8 | ||
![]() |
70d4ee93f5 | ||
![]() |
34a4372190 | ||
![]() |
301543d3d0 | ||
![]() |
c3c2bc51c5 | ||
![]() |
40f808e9be | ||
![]() |
3caf6c0e31 | ||
![]() |
36a0c1b514 | ||
![]() |
e128751e64 | ||
![]() |
fae1efc237 | ||
![]() |
dec03d4d25 | ||
![]() |
064bbab3f5 | ||
![]() |
f03e81544e | ||
![]() |
4c8027aefa | ||
![]() |
dbecd7a99c | ||
![]() |
b035649c75 | ||
![]() |
97ab595e20 | ||
![]() |
20d4031ed4 | ||
![]() |
812be801ce | ||
![]() |
672a7ca740 | ||
![]() |
0b3d69aa8e | ||
![]() |
e87542e091 | ||
![]() |
e9bbf773d6 | ||
![]() |
68e8c968a8 | ||
![]() |
a3f12329b3 | ||
![]() |
f5ef213842 | ||
![]() |
b573e5a2b3 | ||
![]() |
17c3e7b238 | ||
![]() |
9921a67a05 | ||
![]() |
ad09197c00 | ||
![]() |
be11d1cabf | ||
![]() |
5399e2b648 | ||
![]() |
a8d72cfdcf | ||
![]() |
fbeee11fd7 | ||
![]() |
545dae2e7f | ||
![]() |
86891351f6 | ||
![]() |
ddfe790995 | ||
![]() |
85a9a8eca1 | ||
![]() |
52c358e120 | ||
![]() |
f516e538a8 | ||
![]() |
4c28c1f556 | ||
![]() |
b996bd3e65 | ||
![]() |
6c1167df4a | ||
![]() |
1044345587 | ||
![]() |
d9eb419ecc | ||
![]() |
8a266aac34 | ||
![]() |
f6ac5dab74 | ||
![]() |
d34ba16a30 | ||
![]() |
5638e937b0 | ||
![]() |
2ff88e7baf | ||
![]() |
2e1732fadf | ||
![]() |
57e041171b | ||
![]() |
317b73ffaf | ||
![]() |
39a9634a5c | ||
![]() |
22ebc654a7 | ||
![]() |
20c3b9b6f9 | ||
![]() |
7588d83c6c | ||
![]() |
efbb5bf9af | ||
![]() |
dac69fafb8 | ||
![]() |
8d98085873 | ||
![]() |
8950e817e0 | ||
![]() |
6fb1b53039 | ||
![]() |
c81a4f8633 | ||
![]() |
4599d1650b | ||
![]() |
4edc3872ce | ||
![]() |
8999e9f116 | ||
![]() |
c5b4892596 | ||
![]() |
46812777e2 | ||
![]() |
616c0ebaa4 | ||
![]() |
33d0343089 | ||
![]() |
e6af8f63f3 | ||
![]() |
cda62a4ff3 | ||
![]() |
f359d619cb | ||
![]() |
495faf5033 | ||
![]() |
cba2daf314 | ||
![]() |
2d68f9a986 | ||
![]() |
7fde2e2fe0 | ||
![]() |
bd4f3b0553 | ||
![]() |
6373347d65 | ||
![]() |
b458b204f0 | ||
![]() |
dd3a3f821c | ||
![]() |
60e3a1fc5f | ||
![]() |
706a5680e1 | ||
![]() |
cad87f51a3 | ||
![]() |
66ab90b518 | ||
![]() |
8bdd81ff24 | ||
![]() |
f64e542879 | ||
![]() |
7c58476af9 | ||
![]() |
1f3b06a9bd | ||
![]() |
e1314b6cda | ||
![]() |
a6b629c392 | ||
![]() |
f9f51e2381 | ||
![]() |
ee75cba008 | ||
![]() |
c768f03f71 | ||
![]() |
20a57d6381 | ||
![]() |
308f25fe4c | ||
![]() |
9f2ba6bc2c | ||
![]() |
b3b5d9602a | ||
![]() |
2d16732972 | ||
![]() |
94efd3e230 | ||
![]() |
59a690f214 | ||
![]() |
6f70a52880 | ||
![]() |
85ebe5e01a | ||
![]() |
a3ec4db9cc | ||
![]() |
ffa6b5fcb2 | ||
![]() |
b78a1f7b61 | ||
![]() |
1d49c5056c | ||
![]() |
83ebd601a9 | ||
![]() |
a972e295ea | ||
![]() |
7a0b4fc62c | ||
![]() |
d78fcd2a29 | ||
![]() |
2b2f5c9353 | ||
![]() |
fcbb9dd8d8 | ||
![]() |
b34f3ad5c5 | ||
![]() |
8bdd909351 | ||
![]() |
d7fe7f35ad | ||
![]() |
1ebcc34e66 | ||
![]() |
40e83dd9e0 | ||
![]() |
18fd00d0c2 | ||
![]() |
9ec26a9be5 | ||
![]() |
3c0be47d3c | ||
![]() |
26b5dab12b | ||
![]() |
75ae6a8087 | ||
![]() |
fb0e102d74 | ||
![]() |
d777ec3267 | ||
![]() |
cff9e9abab | ||
![]() |
0b19831a7a | ||
![]() |
27f3715780 | ||
![]() |
76858f0534 | ||
![]() |
bcfdfe93f9 | ||
![]() |
7c6cc16ef1 | ||
![]() |
471c68f653 | ||
![]() |
ae102f1318 | ||
![]() |
2a0ad20188 | ||
![]() |
9db5b481be | ||
![]() |
185d00c86c | ||
![]() |
e72ec07683 | ||
![]() |
6e12726b11 | ||
![]() |
6c539cd2d8 | ||
![]() |
77642b9e3d | ||
![]() |
86dc7111cb | ||
![]() |
8d428acbbb | ||
![]() |
1c13851858 | ||
![]() |
a75ebc27c4 | ||
![]() |
45f92dd981 | ||
![]() |
5a6ce86476 | ||
![]() |
7afad1dde9 | ||
![]() |
b766d91f49 | ||
![]() |
22dac266c4 | ||
![]() |
5fb9537d6d | ||
![]() |
c1b24e6ba2 | ||
![]() |
cf803507d6 | ||
![]() |
16e5271cac | ||
![]() |
d5ad35630f | ||
![]() |
1395baef01 | ||
![]() |
90dcb02429 | ||
![]() |
4f0211cdd8 | ||
![]() |
a1e6d4b693 | ||
![]() |
77db88ad28 | ||
![]() |
c6a1b9fc39 | ||
![]() |
9afd270111 |
@@ -79,6 +79,7 @@ components: &components
|
||||
- homeassistant/components/group/**
|
||||
- homeassistant/components/hassio/**
|
||||
- homeassistant/components/homeassistant/**
|
||||
- homeassistant/components/homeassistant_hardware/**
|
||||
- homeassistant/components/http/**
|
||||
- homeassistant/components/image/**
|
||||
- homeassistant/components/input_boolean/**
|
||||
@@ -127,6 +128,7 @@ tests: &tests
|
||||
- tests/*.py
|
||||
- tests/auth/**
|
||||
- tests/backports/**
|
||||
- tests/components/conftest.py
|
||||
- tests/components/diagnostics/**
|
||||
- tests/components/history/**
|
||||
- tests/components/logbook/**
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "Home Assistant Dev",
|
||||
"context": "..",
|
||||
"dockerFile": "../Dockerfile.dev",
|
||||
"postCreateCommand": "script/setup",
|
||||
"postCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && script/setup",
|
||||
"postStartCommand": "script/bootstrap",
|
||||
"containerEnv": {
|
||||
"PYTHONASYNCIODEBUG": "1"
|
||||
@@ -12,7 +12,12 @@
|
||||
},
|
||||
// Port 5683 udp is used by Shelly integration
|
||||
"appPort": ["8123:8123", "5683:5683/udp"],
|
||||
"runArgs": ["-e", "GIT_EDITOR=code --wait"],
|
||||
"runArgs": [
|
||||
"-e",
|
||||
"GIT_EDITOR=code --wait",
|
||||
"--security-opt",
|
||||
"label=disable"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
@@ -53,7 +58,13 @@
|
||||
],
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff"
|
||||
}
|
||||
},
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["homeassistant/components/*/manifest.json"],
|
||||
"url": "./script/json_schemas/manifest_schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ docs
|
||||
# Development
|
||||
.devcontainer
|
||||
.vscode
|
||||
.tool-versions
|
||||
|
||||
# Test related files
|
||||
tests
|
||||
|
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,2 +1 @@
|
||||
custom: https://www.nabucasa.com
|
||||
github: balloob
|
||||
custom: https://www.openhomefoundation.org
|
||||
|
30
.github/workflows/builder.yml
vendored
30
.github/workflows/builder.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
||||
publish: ${{ steps.version.outputs.publish }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
@@ -116,7 +116,7 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
- green
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set build additional args
|
||||
run: |
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize git
|
||||
uses: home-assistant/actions/helpers/git-init@master
|
||||
@@ -321,10 +321,10 @@ jobs:
|
||||
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.6.0
|
||||
uses: sigstore/cosign-installer@v3.7.0
|
||||
with:
|
||||
cosign-release: "v2.2.3"
|
||||
|
||||
@@ -451,10 +451,10 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
@@ -499,7 +499,7 @@ jobs:
|
||||
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
@@ -509,7 +509,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
||||
with:
|
||||
context: . # So action will not pull the repository again
|
||||
file: ./script/hassfest/docker/Dockerfile
|
||||
@@ -522,7 +522,7 @@ jobs:
|
||||
- name: Push Docker image
|
||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||
id: push
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0
|
||||
with:
|
||||
context: . # So action will not pull the repository again
|
||||
file: ./script/hassfest/docker/Dockerfile
|
||||
@@ -531,7 +531,7 @@ jobs:
|
||||
|
||||
- name: Generate artifact attestation
|
||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||
uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3
|
||||
uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4
|
||||
with:
|
||||
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
|
186
.github/workflows/ci.yaml
vendored
186
.github/workflows/ci.yaml
vendored
@@ -37,12 +37,12 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 10
|
||||
CACHE_VERSION: 11
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 9
|
||||
HA_SHORT_VERSION: "2024.10"
|
||||
HA_SHORT_VERSION: "2024.12"
|
||||
DEFAULT_PYTHON: "3.12"
|
||||
ALL_PYTHON_VERSIONS: "['3.12']"
|
||||
ALL_PYTHON_VERSIONS: "['3.12', '3.13']"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
# 10.6 is the current long-term-support
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Generate partial Python venv restore key
|
||||
id: generate_python_cache_key
|
||||
run: |
|
||||
@@ -231,16 +231,16 @@ jobs:
|
||||
- info
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
lookup-only: true
|
||||
@@ -277,16 +277,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -317,16 +317,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -335,7 +335,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -357,16 +357,16 @@ jobs:
|
||||
- pre-commit
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -375,7 +375,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -447,7 +447,7 @@ jobs:
|
||||
- script/hassfest/docker/Dockerfile
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Register hadolint problem matcher
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
|
||||
@@ -466,10 +466,10 @@ jobs:
|
||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
@@ -482,7 +482,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
lookup-only: true
|
||||
@@ -491,7 +491,7 @@ jobs:
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore uv wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: >-
|
||||
@@ -550,16 +550,16 @@ jobs:
|
||||
sudo apt-get -y install \
|
||||
libturbojpeg
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -583,16 +583,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -615,37 +615,41 @@ jobs:
|
||||
&& github.event.inputs.mypy-only != 'true'
|
||||
|| github.event.inputs.audit-licenses-only == 'true')
|
||||
&& needs.info.outputs.requirements == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.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
|
||||
- name: Extract license data
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pip-licenses --format=json --output-file=licenses.json
|
||||
python -m script.licenses extract --output-file=licenses-${{ matrix.python-version }}.json
|
||||
- name: Upload licenses
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: licenses
|
||||
path: licenses.json
|
||||
- name: Process licenses
|
||||
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
|
||||
path: licenses-${{ matrix.python-version }}.json
|
||||
- name: Check licenses
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
python -m script.licenses
|
||||
python -m script.licenses check licenses-${{ matrix.python-version }}.json
|
||||
|
||||
pylint:
|
||||
name: Check pylint
|
||||
@@ -660,16 +664,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -707,16 +711,16 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -752,10 +756,10 @@ jobs:
|
||||
- base
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -768,7 +772,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -776,7 +780,7 @@ jobs:
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@v4.0.2
|
||||
uses: actions/cache@v4.1.2
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
@@ -827,16 +831,16 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -848,7 +852,7 @@ jobs:
|
||||
. venv/bin/activate
|
||||
python -m script.split_tests ${{ needs.info.outputs.test_group_count }} tests
|
||||
- name: Upload pytest_buckets
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: pytest_buckets
|
||||
path: pytest_buckets.txt
|
||||
@@ -891,16 +895,16 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -940,7 +944,8 @@ jobs:
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
--durations=10 \
|
||||
-n auto \
|
||||
--numprocesses auto \
|
||||
--snapshot-details \
|
||||
--dist=loadfile \
|
||||
${cov_params[@]} \
|
||||
-o console_output_style=count \
|
||||
@@ -949,14 +954,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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
@@ -1011,16 +1016,16 @@ jobs:
|
||||
libturbojpeg \
|
||||
libmariadb-dev-compat
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1062,7 +1067,8 @@ jobs:
|
||||
python3 -b -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=20 \
|
||||
-n 1 \
|
||||
--numprocesses 1 \
|
||||
--snapshot-details \
|
||||
${cov_params[@]} \
|
||||
-o console_output_style=count \
|
||||
--durations=10 \
|
||||
@@ -1075,7 +1081,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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
@@ -1083,7 +1089,7 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.mariadb }}
|
||||
@@ -1094,7 +1100,7 @@ jobs:
|
||||
./script/check_dirty
|
||||
|
||||
pytest-postgres:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
services:
|
||||
postgres:
|
||||
image: ${{ matrix.postgresql-group }}
|
||||
@@ -1134,19 +1140,21 @@ jobs:
|
||||
sudo apt-get -y install \
|
||||
bluez \
|
||||
ffmpeg \
|
||||
libturbojpeg \
|
||||
libturbojpeg
|
||||
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
|
||||
sudo apt-get -y install \
|
||||
postgresql-server-dev-14
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1188,7 +1196,8 @@ jobs:
|
||||
python3 -b -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
-n 1 \
|
||||
--numprocesses 1 \
|
||||
--snapshot-details \
|
||||
${cov_params[@]} \
|
||||
-o console_output_style=count \
|
||||
--durations=0 \
|
||||
@@ -1202,7 +1211,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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
@@ -1210,7 +1219,7 @@ jobs:
|
||||
overwrite: true
|
||||
- name: Upload coverage artifact
|
||||
if: needs.info.outputs.skip_coverage != 'true'
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{
|
||||
steps.pytest-partial.outputs.postgresql }}
|
||||
@@ -1232,14 +1241,14 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
uses: codecov/codecov-action@v4.6.0
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
flags: full-suite
|
||||
@@ -1283,16 +1292,16 @@ jobs:
|
||||
libturbojpeg \
|
||||
libgammu-dev
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.0.2
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1334,7 +1343,8 @@ jobs:
|
||||
python3 -b -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
-n auto \
|
||||
--numprocesses auto \
|
||||
--snapshot-details \
|
||||
${cov_params[@]} \
|
||||
-o console_output_style=count \
|
||||
--durations=0 \
|
||||
@@ -1344,14 +1354,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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
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.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
|
||||
path: coverage.xml
|
||||
@@ -1370,14 +1380,14 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
with:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
uses: codecov/codecov-action@v4.6.0
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.26.9
|
||||
uses: github/codeql-action/init@v3.27.1
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.26.9
|
||||
uses: github/codeql-action/analyze@v3.27.1
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
4
.github/workflows/translations.yml
vendored
4
.github/workflows/translations.yml
vendored
@@ -19,10 +19,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
|
51
.github/workflows/wheels.yml
vendored
51
.github/workflows/wheels.yml
vendored
@@ -32,11 +32,11 @@ jobs:
|
||||
architectures: ${{ steps.info.outputs.architectures }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
id: python
|
||||
uses: actions/setup-python@v5.2.0
|
||||
uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
check-latest: true
|
||||
@@ -64,11 +64,8 @@ jobs:
|
||||
- name: Write env-file
|
||||
run: |
|
||||
(
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
@@ -82,7 +79,7 @@ jobs:
|
||||
) > .env_file
|
||||
|
||||
- name: Upload env_file
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: env_file
|
||||
path: ./.env_file
|
||||
@@ -90,7 +87,7 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: requirements_diff
|
||||
path: ./requirements_diff.txt
|
||||
@@ -102,7 +99,7 @@ jobs:
|
||||
python -m script.gen_requirements_all ci
|
||||
|
||||
- name: Upload requirements_all_wheels
|
||||
uses: actions/upload-artifact@v4.4.0
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: requirements_all_wheels
|
||||
path: ./requirements_all_wheels_*.txt
|
||||
@@ -115,11 +112,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp312"]
|
||||
abi: ["cp312", "cp313"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
@@ -138,14 +135,14 @@ jobs:
|
||||
sed -i "/uv/d" requirements_diff.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "libffi-dev;openssl-dev;yaml-dev;nasm"
|
||||
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-dev"
|
||||
skip-binary: aiohttp;multidict;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
@@ -159,11 +156,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp312"]
|
||||
abi: ["cp312", "cp313"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.7
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Download env_file
|
||||
uses: actions/download-artifact@v4.1.8
|
||||
@@ -201,19 +198,19 @@ jobs:
|
||||
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all_wheels_${{ matrix.arch }}.txt requirements_all.txt
|
||||
|
||||
- name: Create requirements for cython<3
|
||||
if: matrix.abi == 'cp312'
|
||||
run: |
|
||||
# Some dependencies still require 'cython<3'
|
||||
# and don't yet use isolated build environments.
|
||||
# Build these first.
|
||||
# grpcio: https://github.com/grpc/grpc/issues/33918
|
||||
# pydantic: https://github.com/pydantic/pydantic/issues/7689
|
||||
|
||||
touch requirements_old-cython.txt
|
||||
cat homeassistant/package_constraints.txt | grep 'grpcio==' >> requirements_old-cython.txt
|
||||
cat homeassistant/package_constraints.txt | grep 'pydantic==' >> requirements_old-cython.txt
|
||||
|
||||
- name: Build wheels (old cython)
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
if: matrix.abi == 'cp312'
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
@@ -221,50 +218,50 @@ 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;multidict;SQLAlchemy;protobuf;pydantic;pymicro-vad;yarl
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pydantic;pymicro-vad;yarl
|
||||
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.07.1
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
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;multidict;SQLAlchemy;protobuf;pydantic;pymicro-vad;yarl
|
||||
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;zlib-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtaa"
|
||||
|
||||
- name: Build wheels (part 2)
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
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;multidict;SQLAlchemy;protobuf;pydantic;pymicro-vad;yarl
|
||||
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;zlib-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtab"
|
||||
|
||||
- name: Build wheels (part 3)
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
uses: home-assistant/wheels@2024.11.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
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;multidict;SQLAlchemy;protobuf;pydantic;pymicro-vad;yarl
|
||||
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;zlib-dev"
|
||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtac"
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -79,6 +79,7 @@ pytest-*.txt
|
||||
.pydevproject
|
||||
|
||||
.python-version
|
||||
.tool-versions
|
||||
|
||||
# emacs auto backups
|
||||
*~
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.6.6
|
||||
rev: v0.7.2
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
@@ -83,10 +83,10 @@ repos:
|
||||
pass_filenames: false
|
||||
language: script
|
||||
types: [text]
|
||||
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements\.txt)$
|
||||
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
|
||||
- id: hassfest-metadata
|
||||
name: hassfest-metadata
|
||||
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata
|
||||
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata,docker
|
||||
pass_filenames: false
|
||||
language: script
|
||||
types: [text]
|
||||
|
@@ -124,6 +124,7 @@ homeassistant.components.bryant_evolution.*
|
||||
homeassistant.components.bthome.*
|
||||
homeassistant.components.button.*
|
||||
homeassistant.components.calendar.*
|
||||
homeassistant.components.cambridge_audio.*
|
||||
homeassistant.components.camera.*
|
||||
homeassistant.components.canary.*
|
||||
homeassistant.components.cert_expiry.*
|
||||
@@ -208,12 +209,14 @@ homeassistant.components.geo_location.*
|
||||
homeassistant.components.geocaching.*
|
||||
homeassistant.components.gios.*
|
||||
homeassistant.components.glances.*
|
||||
homeassistant.components.go2rtc.*
|
||||
homeassistant.components.goalzero.*
|
||||
homeassistant.components.google.*
|
||||
homeassistant.components.google_assistant_sdk.*
|
||||
homeassistant.components.google_cloud.*
|
||||
homeassistant.components.google_photos.*
|
||||
homeassistant.components.google_sheets.*
|
||||
homeassistant.components.govee_ble.*
|
||||
homeassistant.components.gpsd.*
|
||||
homeassistant.components.greeneye_monitor.*
|
||||
homeassistant.components.group.*
|
||||
@@ -301,7 +304,6 @@ homeassistant.components.lookin.*
|
||||
homeassistant.components.luftdaten.*
|
||||
homeassistant.components.madvr.*
|
||||
homeassistant.components.manual.*
|
||||
homeassistant.components.map.*
|
||||
homeassistant.components.mastodon.*
|
||||
homeassistant.components.matrix.*
|
||||
homeassistant.components.matter.*
|
||||
@@ -322,11 +324,13 @@ homeassistant.components.moon.*
|
||||
homeassistant.components.mopeka.*
|
||||
homeassistant.components.motionmount.*
|
||||
homeassistant.components.mqtt.*
|
||||
homeassistant.components.music_assistant.*
|
||||
homeassistant.components.my.*
|
||||
homeassistant.components.mysensors.*
|
||||
homeassistant.components.myuplink.*
|
||||
homeassistant.components.nam.*
|
||||
homeassistant.components.nanoleaf.*
|
||||
homeassistant.components.nasweb.*
|
||||
homeassistant.components.neato.*
|
||||
homeassistant.components.nest.*
|
||||
homeassistant.components.netatmo.*
|
||||
@@ -336,6 +340,7 @@ homeassistant.components.nfandroidtv.*
|
||||
homeassistant.components.nightscout.*
|
||||
homeassistant.components.nissan_leaf.*
|
||||
homeassistant.components.no_ip.*
|
||||
homeassistant.components.nordpool.*
|
||||
homeassistant.components.notify.*
|
||||
homeassistant.components.notion.*
|
||||
homeassistant.components.number.*
|
||||
@@ -345,6 +350,7 @@ homeassistant.components.oncue.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.onkyo.*
|
||||
homeassistant.components.open_meteo.*
|
||||
homeassistant.components.openai_conversation.*
|
||||
homeassistant.components.openexchangerates.*
|
||||
homeassistant.components.opensky.*
|
||||
homeassistant.components.openuv.*
|
||||
@@ -352,6 +358,7 @@ homeassistant.components.oralb.*
|
||||
homeassistant.components.otbr.*
|
||||
homeassistant.components.overkiz.*
|
||||
homeassistant.components.p1_monitor.*
|
||||
homeassistant.components.panel_custom.*
|
||||
homeassistant.components.peco.*
|
||||
homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
@@ -369,6 +376,7 @@ homeassistant.components.pvoutput.*
|
||||
homeassistant.components.qnap_qsw.*
|
||||
homeassistant.components.rabbitair.*
|
||||
homeassistant.components.radarr.*
|
||||
homeassistant.components.radio_browser.*
|
||||
homeassistant.components.rainforest_raven.*
|
||||
homeassistant.components.rainmachine.*
|
||||
homeassistant.components.raspberry_pi.*
|
||||
@@ -406,6 +414,7 @@ homeassistant.components.sensor.*
|
||||
homeassistant.components.sensoterra.*
|
||||
homeassistant.components.senz.*
|
||||
homeassistant.components.sfr_box.*
|
||||
homeassistant.components.shell_command.*
|
||||
homeassistant.components.shelly.*
|
||||
homeassistant.components.shopping_list.*
|
||||
homeassistant.components.simplepush.*
|
||||
@@ -420,6 +429,7 @@ homeassistant.components.snooz.*
|
||||
homeassistant.components.solarlog.*
|
||||
homeassistant.components.sonarr.*
|
||||
homeassistant.components.speedtestdotnet.*
|
||||
homeassistant.components.spotify.*
|
||||
homeassistant.components.sql.*
|
||||
homeassistant.components.squeezebox.*
|
||||
homeassistant.components.ssdp.*
|
||||
@@ -434,6 +444,7 @@ homeassistant.components.suez_water.*
|
||||
homeassistant.components.sun.*
|
||||
homeassistant.components.surepetcare.*
|
||||
homeassistant.components.switch.*
|
||||
homeassistant.components.switch_as_x.*
|
||||
homeassistant.components.switchbee.*
|
||||
homeassistant.components.switchbot_cloud.*
|
||||
homeassistant.components.switcher_kis.*
|
||||
@@ -502,6 +513,7 @@ homeassistant.components.whois.*
|
||||
homeassistant.components.withings.*
|
||||
homeassistant.components.wiz.*
|
||||
homeassistant.components.wled.*
|
||||
homeassistant.components.workday.*
|
||||
homeassistant.components.worldclock.*
|
||||
homeassistant.components.xiaomi_ble.*
|
||||
homeassistant.components.yale_smart_alarm.*
|
||||
|
10
.vscode/settings.default.json
vendored
10
.vscode/settings.default.json
vendored
@@ -6,5 +6,13 @@
|
||||
// https://code.visualstudio.com/docs/python/testing#_pytest-configuration-settings
|
||||
"python.testing.pytestEnabled": false,
|
||||
// https://code.visualstudio.com/docs/python/linting#_general-settings
|
||||
"pylint.importStrategy": "fromEnvironment"
|
||||
"pylint.importStrategy": "fromEnvironment",
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"homeassistant/components/*/manifest.json"
|
||||
],
|
||||
"url": "./script/json_schemas/manifest_schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
40
CODEOWNERS
40
CODEOWNERS
@@ -496,8 +496,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/freebox/ @hacf-fr @Quentame
|
||||
/homeassistant/components/freedompro/ @stefano055415
|
||||
/tests/components/freedompro/ @stefano055415
|
||||
/homeassistant/components/fritz/ @mammuth @AaronDavidSchneider @chemelli74 @mib1185
|
||||
/tests/components/fritz/ @mammuth @AaronDavidSchneider @chemelli74 @mib1185
|
||||
/homeassistant/components/fritz/ @AaronDavidSchneider @chemelli74 @mib1185
|
||||
/tests/components/fritz/ @AaronDavidSchneider @chemelli74 @mib1185
|
||||
/homeassistant/components/fritzbox/ @mib1185 @flabbamann
|
||||
/tests/components/fritzbox/ @mib1185 @flabbamann
|
||||
/homeassistant/components/fritzbox_callmonitor/ @cdce8p
|
||||
@@ -544,6 +544,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/github/ @timmo001 @ludeeus
|
||||
/homeassistant/components/glances/ @engrbm87
|
||||
/tests/components/glances/ @engrbm87
|
||||
/homeassistant/components/go2rtc/ @home-assistant/core
|
||||
/tests/components/go2rtc/ @home-assistant/core
|
||||
/homeassistant/components/goalzero/ @tkdrob
|
||||
/tests/components/goalzero/ @tkdrob
|
||||
/homeassistant/components/gogogate2/ @vangorra
|
||||
@@ -615,8 +617,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/hlk_sw16/ @jameshilliard
|
||||
/homeassistant/components/holiday/ @jrieger @gjohansson-ST
|
||||
/tests/components/holiday/ @jrieger @gjohansson-ST
|
||||
/homeassistant/components/home_connect/ @DavidMStraub
|
||||
/tests/components/home_connect/ @DavidMStraub
|
||||
/homeassistant/components/home_connect/ @DavidMStraub @Diegorro98
|
||||
/tests/components/home_connect/ @DavidMStraub @Diegorro98
|
||||
/homeassistant/components/homeassistant/ @home-assistant/core
|
||||
/tests/components/homeassistant/ @home-assistant/core
|
||||
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
|
||||
@@ -657,6 +659,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/hunterdouglas_powerview/ @bdraco @kingy444 @trullock
|
||||
/homeassistant/components/husqvarna_automower/ @Thomas55555
|
||||
/tests/components/husqvarna_automower/ @Thomas55555
|
||||
/homeassistant/components/husqvarna_automower_ble/ @alistair23
|
||||
/tests/components/husqvarna_automower_ble/ @alistair23
|
||||
/homeassistant/components/huum/ @frwickst
|
||||
/tests/components/huum/ @frwickst
|
||||
/homeassistant/components/hvv_departures/ @vigonotion
|
||||
@@ -817,6 +821,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/lektrico/ @lektrico
|
||||
/homeassistant/components/lg_netcast/ @Drafteed @splinter98
|
||||
/tests/components/lg_netcast/ @Drafteed @splinter98
|
||||
/homeassistant/components/lg_thinq/ @LG-ThinQ-Integration
|
||||
/tests/components/lg_thinq/ @LG-ThinQ-Integration
|
||||
/homeassistant/components/lidarr/ @tkdrob
|
||||
/tests/components/lidarr/ @tkdrob
|
||||
/homeassistant/components/lifx/ @Djelibeybi
|
||||
@@ -948,6 +954,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/msteams/ @peroyvind
|
||||
/homeassistant/components/mullvad/ @meichthys
|
||||
/tests/components/mullvad/ @meichthys
|
||||
/homeassistant/components/music_assistant/ @music-assistant
|
||||
/tests/components/music_assistant/ @music-assistant
|
||||
/homeassistant/components/mutesync/ @currentoor
|
||||
/tests/components/mutesync/ @currentoor
|
||||
/homeassistant/components/my/ @home-assistant/core
|
||||
@@ -962,6 +970,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/nam/ @bieniu
|
||||
/homeassistant/components/nanoleaf/ @milanmeu @joostlek
|
||||
/tests/components/nanoleaf/ @milanmeu @joostlek
|
||||
/homeassistant/components/nasweb/ @nasWebio
|
||||
/tests/components/nasweb/ @nasWebio
|
||||
/homeassistant/components/neato/ @Santobert
|
||||
/tests/components/neato/ @Santobert
|
||||
/homeassistant/components/nederlandse_spoorwegen/ @YarmoM
|
||||
@@ -1002,6 +1012,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/noaa_tides/ @jdelaney72
|
||||
/homeassistant/components/nobo_hub/ @echoromeo @oyvindwe
|
||||
/tests/components/nobo_hub/ @echoromeo @oyvindwe
|
||||
/homeassistant/components/nordpool/ @gjohansson-ST
|
||||
/tests/components/nordpool/ @gjohansson-ST
|
||||
/homeassistant/components/notify/ @home-assistant/core
|
||||
/tests/components/notify/ @home-assistant/core
|
||||
/homeassistant/components/notify_events/ @matrozov @papajojo
|
||||
@@ -1045,6 +1057,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/onewire/ @garbled1 @epenet
|
||||
/tests/components/onewire/ @garbled1 @epenet
|
||||
/homeassistant/components/onkyo/ @arturpragacz
|
||||
/tests/components/onkyo/ @arturpragacz
|
||||
/homeassistant/components/onvif/ @hunterjm
|
||||
/tests/components/onvif/ @hunterjm
|
||||
/homeassistant/components/open_meteo/ @frenck
|
||||
@@ -1086,10 +1099,10 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ovo_energy/ @timmo001
|
||||
/homeassistant/components/p1_monitor/ @klaasnicolaas
|
||||
/tests/components/p1_monitor/ @klaasnicolaas
|
||||
/homeassistant/components/palazzetti/ @dotvav
|
||||
/tests/components/palazzetti/ @dotvav
|
||||
/homeassistant/components/panel_custom/ @home-assistant/frontend
|
||||
/tests/components/panel_custom/ @home-assistant/frontend
|
||||
/homeassistant/components/panel_iframe/ @home-assistant/frontend
|
||||
/tests/components/panel_iframe/ @home-assistant/frontend
|
||||
/homeassistant/components/peco/ @IceBotYT
|
||||
/tests/components/peco/ @IceBotYT
|
||||
/homeassistant/components/pegel_online/ @mib1185
|
||||
@@ -1237,8 +1250,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/roku/ @ctalkington
|
||||
/homeassistant/components/romy/ @xeniter
|
||||
/tests/components/romy/ @xeniter
|
||||
/homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn @Xitee1 @Orhideous
|
||||
/tests/components/roomba/ @pschmitt @cyr-ius @shenxn @Xitee1 @Orhideous
|
||||
/homeassistant/components/roomba/ @pschmitt @cyr-ius @shenxn @Orhideous
|
||||
/tests/components/roomba/ @pschmitt @cyr-ius @shenxn @Orhideous
|
||||
/homeassistant/components/roon/ @pavoni
|
||||
/tests/components/roon/ @pavoni
|
||||
/homeassistant/components/rpi_power/ @shenxn @swetoast
|
||||
@@ -1349,6 +1362,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/smarttub/ @mdz
|
||||
/tests/components/smarttub/ @mdz
|
||||
/homeassistant/components/smarty/ @z0mbieprocess
|
||||
/tests/components/smarty/ @z0mbieprocess
|
||||
/homeassistant/components/smhi/ @gjohansson-ST
|
||||
/tests/components/smhi/ @gjohansson-ST
|
||||
/homeassistant/components/smlight/ @tl-sl
|
||||
@@ -1382,15 +1396,13 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/spaceapi/ @fabaff
|
||||
/homeassistant/components/speedtestdotnet/ @rohankapoorcom @engrbm87
|
||||
/tests/components/speedtestdotnet/ @rohankapoorcom @engrbm87
|
||||
/homeassistant/components/spider/ @peternijssen
|
||||
/tests/components/spider/ @peternijssen
|
||||
/homeassistant/components/splunk/ @Bre77
|
||||
/homeassistant/components/spotify/ @frenck @joostlek
|
||||
/tests/components/spotify/ @frenck @joostlek
|
||||
/homeassistant/components/sql/ @gjohansson-ST @dougiteixeira
|
||||
/tests/components/sql/ @gjohansson-ST @dougiteixeira
|
||||
/homeassistant/components/squeezebox/ @rajlaud
|
||||
/tests/components/squeezebox/ @rajlaud
|
||||
/homeassistant/components/squeezebox/ @rajlaud @pssc @peteS-UK
|
||||
/tests/components/squeezebox/ @rajlaud @pssc @peteS-UK
|
||||
/homeassistant/components/srp_energy/ @briglx
|
||||
/tests/components/srp_energy/ @briglx
|
||||
/homeassistant/components/starline/ @anonym-tsk
|
||||
@@ -1414,8 +1426,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/stt/ @home-assistant/core
|
||||
/homeassistant/components/subaru/ @G-Two
|
||||
/tests/components/subaru/ @G-Two
|
||||
/homeassistant/components/suez_water/ @ooii
|
||||
/tests/components/suez_water/ @ooii
|
||||
/homeassistant/components/suez_water/ @ooii @jb101010-2
|
||||
/tests/components/suez_water/ @ooii @jb101010-2
|
||||
/homeassistant/components/sun/ @Swamp-Ig
|
||||
/tests/components/sun/ @Swamp-Ig
|
||||
/homeassistant/components/sunweg/ @rokam
|
||||
|
20
Dockerfile
20
Dockerfile
@@ -7,12 +7,13 @@ FROM ${BUILD_FROM}
|
||||
# Synchronize with homeassistant/core.py:async_stop
|
||||
ENV \
|
||||
S6_SERVICES_GRACETIME=240000 \
|
||||
UV_SYSTEM_PYTHON=true
|
||||
UV_SYSTEM_PYTHON=true \
|
||||
UV_NO_CACHE=true
|
||||
|
||||
ARG QEMU_CPU
|
||||
|
||||
# Install uv
|
||||
RUN pip3 install uv==0.4.15
|
||||
RUN pip3 install uv==0.5.0
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
@@ -44,4 +45,19 @@ RUN \
|
||||
# Home Assistant S6-Overlay
|
||||
COPY rootfs /
|
||||
|
||||
# Needs to be redefined inside the FROM statement to be set for RUN commands
|
||||
ARG BUILD_ARCH
|
||||
# Get go2rtc binary
|
||||
RUN \
|
||||
case "${BUILD_ARCH}" in \
|
||||
"aarch64") go2rtc_suffix='arm64' ;; \
|
||||
"armhf") go2rtc_suffix='armv6' ;; \
|
||||
"armv7") go2rtc_suffix='arm' ;; \
|
||||
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
||||
esac \
|
||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.6/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||
&& chmod +x /bin/go2rtc \
|
||||
# Verify go2rtc can be executed
|
||||
&& go2rtc --version
|
||||
|
||||
WORKDIR /config
|
||||
|
@@ -7,8 +7,6 @@ Check out `home-assistant.io <https://home-assistant.io>`__ for `a
|
||||
demo <https://demo.home-assistant.io>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
|
||||
`tutorials <https://home-assistant.io/getting-started/automation/>`__ and `documentation <https://home-assistant.io/docs/>`__.
|
||||
|
||||
This is a project of the `Open Home Foundation <https://www.openhomefoundation.org/>`__.
|
||||
|
||||
|screenshot-states|
|
||||
|
||||
Featured integrations
|
||||
@@ -22,9 +20,14 @@ components <https://developers.home-assistant.io/docs/creating_component_index/>
|
||||
If you run into issues while using Home Assistant or during development
|
||||
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
|
||||
|
||||
|ohf-logo|
|
||||
|
||||
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
|
||||
:target: https://www.home-assistant.io/join-chat/
|
||||
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-states.png
|
||||
:target: https://demo.home-assistant.io
|
||||
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-integrations.png
|
||||
:target: https://home-assistant.io/integrations/
|
||||
.. |ohf-logo| image:: https://www.openhomefoundation.org/badges/home-assistant.png
|
||||
:alt: Home Assistant - A project from the Open Home Foundation
|
||||
:target: https://www.openhomefoundation.org/
|
||||
|
@@ -9,6 +9,7 @@ import os
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from .backup_restore import restore_backup
|
||||
from .const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
||||
|
||||
FAULT_LOG_FILENAME = "home-assistant.log.fault"
|
||||
@@ -182,6 +183,9 @@ def main() -> int:
|
||||
return scripts.run(args.script)
|
||||
|
||||
config_dir = os.path.abspath(os.path.join(os.getcwd(), args.config))
|
||||
if restore_backup(config_dir):
|
||||
return RESTART_EXIT_CODE
|
||||
|
||||
ensure_config_path(config_dir)
|
||||
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
|
@@ -12,7 +12,6 @@ from typing import Any, cast
|
||||
|
||||
import jwt
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
HassJob,
|
||||
@@ -20,13 +19,14 @@ from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.data_entry_flow import FlowHandler, FlowManager, FlowResultType
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import auth_store, jwt_wrapper, models
|
||||
from .const import ACCESS_TOKEN_EXPIRATION, GROUP_ID_ADMIN, REFRESH_TOKEN_EXPIRATION
|
||||
from .mfa_modules import MultiFactorAuthModule, auth_mfa_module_from_config
|
||||
from .models import AuthFlowResult
|
||||
from .models import AuthFlowContext, AuthFlowResult
|
||||
from .providers import AuthProvider, LoginFlow, auth_provider_from_config
|
||||
from .providers.homeassistant import HassAuthProvider
|
||||
|
||||
@@ -98,7 +98,7 @@ async def auth_manager_from_config(
|
||||
|
||||
|
||||
class AuthManagerFlowManager(
|
||||
data_entry_flow.FlowManager[AuthFlowResult, tuple[str, str]]
|
||||
FlowManager[AuthFlowContext, AuthFlowResult, tuple[str, str]]
|
||||
):
|
||||
"""Manage authentication flows."""
|
||||
|
||||
@@ -113,7 +113,7 @@ class AuthManagerFlowManager(
|
||||
self,
|
||||
handler_key: tuple[str, str],
|
||||
*,
|
||||
context: dict[str, Any] | None = None,
|
||||
context: AuthFlowContext | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> LoginFlow:
|
||||
"""Create a login flow."""
|
||||
@@ -124,7 +124,7 @@ class AuthManagerFlowManager(
|
||||
|
||||
async def async_finish_flow(
|
||||
self,
|
||||
flow: data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]],
|
||||
flow: FlowHandler[AuthFlowContext, AuthFlowResult, tuple[str, str]],
|
||||
result: AuthFlowResult,
|
||||
) -> AuthFlowResult:
|
||||
"""Return a user as result of login flow.
|
||||
@@ -134,7 +134,7 @@ class AuthManagerFlowManager(
|
||||
"""
|
||||
flow = cast(LoginFlow, flow)
|
||||
|
||||
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
|
||||
if result["type"] != FlowResultType.CREATE_ENTRY:
|
||||
return result
|
||||
|
||||
# we got final result
|
||||
|
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from functools import cached_property
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
import secrets
|
||||
from typing import Any, NamedTuple
|
||||
import uuid
|
||||
@@ -11,9 +11,10 @@ import uuid
|
||||
import attr
|
||||
from attr import Attribute
|
||||
from attr.setters import validate
|
||||
from propcache import cached_property
|
||||
|
||||
from homeassistant.const import __version__
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.data_entry_flow import FlowContext, FlowResult
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import permissions as perm_mdl
|
||||
@@ -23,7 +24,16 @@ TOKEN_TYPE_NORMAL = "normal"
|
||||
TOKEN_TYPE_SYSTEM = "system"
|
||||
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token"
|
||||
|
||||
AuthFlowResult = FlowResult[tuple[str, str]]
|
||||
|
||||
class AuthFlowContext(FlowContext, total=False):
|
||||
"""Typed context dict for auth flow."""
|
||||
|
||||
credential_only: bool
|
||||
ip_address: IPv4Address | IPv6Address
|
||||
redirect_uri: str
|
||||
|
||||
|
||||
AuthFlowResult = FlowResult[AuthFlowContext, tuple[str, str]]
|
||||
|
||||
|
||||
@attr.s(slots=True)
|
||||
|
@@ -10,9 +10,10 @@ from typing import Any
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant import data_entry_flow, requirements
|
||||
from homeassistant import requirements
|
||||
from homeassistant.const import CONF_ID, CONF_NAME, CONF_TYPE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import FlowHandler
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.importlib import async_import_module
|
||||
from homeassistant.util import dt as dt_util
|
||||
@@ -21,7 +22,14 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from ..auth_store import AuthStore
|
||||
from ..const import MFA_SESSION_EXPIRATION
|
||||
from ..models import AuthFlowResult, Credentials, RefreshToken, User, UserMeta
|
||||
from ..models import (
|
||||
AuthFlowContext,
|
||||
AuthFlowResult,
|
||||
Credentials,
|
||||
RefreshToken,
|
||||
User,
|
||||
UserMeta,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DATA_REQS: HassKey[set[str]] = HassKey("auth_prov_reqs_processed")
|
||||
@@ -97,7 +105,7 @@ class AuthProvider:
|
||||
|
||||
# Implement by extending class
|
||||
|
||||
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
|
||||
"""Return the data flow for logging in with auth provider.
|
||||
|
||||
Auth provider should extend LoginFlow and return an instance.
|
||||
@@ -184,7 +192,7 @@ async def load_auth_provider_module(
|
||||
return module
|
||||
|
||||
|
||||
class LoginFlow(data_entry_flow.FlowHandler[AuthFlowResult, tuple[str, str]]):
|
||||
class LoginFlow(FlowHandler[AuthFlowContext, AuthFlowResult, tuple[str, str]]):
|
||||
"""Handler for the login flow."""
|
||||
|
||||
_flow_result = AuthFlowResult
|
||||
|
@@ -13,7 +13,7 @@ import voluptuous as vol
|
||||
from homeassistant.const import CONF_COMMAND
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||
from ..models import AuthFlowContext, AuthFlowResult, Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
CONF_ARGS = "args"
|
||||
@@ -59,7 +59,7 @@ class CommandLineAuthProvider(AuthProvider):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._user_meta: dict[str, dict[str, Any]] = {}
|
||||
|
||||
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
|
||||
"""Return a flow to login."""
|
||||
return CommandLineLoginFlow(self)
|
||||
|
||||
|
@@ -17,7 +17,7 @@ from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||
from ..models import AuthFlowContext, AuthFlowResult, Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
STORAGE_VERSION = 1
|
||||
@@ -305,7 +305,7 @@ class HassAuthProvider(AuthProvider):
|
||||
await data.async_load()
|
||||
self.data = data
|
||||
|
||||
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
|
||||
"""Return a flow to login."""
|
||||
return HassLoginFlow(self)
|
||||
|
||||
|
@@ -4,14 +4,14 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import hmac
|
||||
from typing import Any, cast
|
||||
from typing import cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from ..models import AuthFlowResult, Credentials, UserMeta
|
||||
from ..models import AuthFlowContext, AuthFlowResult, Credentials, UserMeta
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
USER_SCHEMA = vol.Schema(
|
||||
@@ -36,7 +36,7 @@ class InvalidAuthError(HomeAssistantError):
|
||||
class ExampleAuthProvider(AuthProvider):
|
||||
"""Example auth provider based on hardcoded usernames and passwords."""
|
||||
|
||||
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
|
||||
"""Return a flow to login."""
|
||||
return ExampleLoginFlow(self)
|
||||
|
||||
|
@@ -25,7 +25,13 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.network import is_cloud_connection
|
||||
|
||||
from .. import InvalidAuthError
|
||||
from ..models import AuthFlowResult, Credentials, RefreshToken, UserMeta
|
||||
from ..models import (
|
||||
AuthFlowContext,
|
||||
AuthFlowResult,
|
||||
Credentials,
|
||||
RefreshToken,
|
||||
UserMeta,
|
||||
)
|
||||
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
|
||||
|
||||
type IPAddress = IPv4Address | IPv6Address
|
||||
@@ -98,7 +104,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
|
||||
"""Trusted Networks auth provider does not support MFA."""
|
||||
return False
|
||||
|
||||
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
|
||||
async def async_login_flow(self, context: AuthFlowContext | None) -> LoginFlow:
|
||||
"""Return a flow to login."""
|
||||
assert context is not None
|
||||
ip_addr = cast(IPAddress, context.get("ip_address"))
|
||||
|
@@ -9,6 +9,7 @@ import it.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# pylint: disable-next=hass-deprecated-import
|
||||
from functools import cached_property as _cached_property, partial
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
|
126
homeassistant/backup_restore.py
Normal file
126
homeassistant/backup_restore.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Home Assistant module to handle restoring backups."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
import securetar
|
||||
|
||||
from .const import __version__ as HA_VERSION
|
||||
|
||||
RESTORE_BACKUP_FILE = ".HA_RESTORE"
|
||||
KEEP_PATHS = ("backups",)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RestoreBackupFileContent:
|
||||
"""Definition for restore backup file content."""
|
||||
|
||||
backup_file_path: Path
|
||||
|
||||
|
||||
def restore_backup_file_content(config_dir: Path) -> RestoreBackupFileContent | None:
|
||||
"""Return the contents of the restore backup file."""
|
||||
instruction_path = config_dir.joinpath(RESTORE_BACKUP_FILE)
|
||||
try:
|
||||
instruction_content = json.loads(instruction_path.read_text(encoding="utf-8"))
|
||||
return RestoreBackupFileContent(
|
||||
backup_file_path=Path(instruction_content["path"])
|
||||
)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def _clear_configuration_directory(config_dir: Path) -> None:
|
||||
"""Delete all files and directories in the config directory except for the backups directory."""
|
||||
keep_paths = [config_dir.joinpath(path) for path in KEEP_PATHS]
|
||||
config_contents = sorted(
|
||||
[entry for entry in config_dir.iterdir() if entry not in keep_paths]
|
||||
)
|
||||
|
||||
for entry in config_contents:
|
||||
entrypath = config_dir.joinpath(entry)
|
||||
|
||||
if entrypath.is_file():
|
||||
entrypath.unlink()
|
||||
elif entrypath.is_dir():
|
||||
shutil.rmtree(entrypath)
|
||||
|
||||
|
||||
def _extract_backup(config_dir: Path, backup_file_path: Path) -> None:
|
||||
"""Extract the backup file to the config directory."""
|
||||
with (
|
||||
TemporaryDirectory() as tempdir,
|
||||
securetar.SecureTarFile(
|
||||
backup_file_path,
|
||||
gzip=False,
|
||||
mode="r",
|
||||
) as ostf,
|
||||
):
|
||||
ostf.extractall(
|
||||
path=Path(tempdir, "extracted"),
|
||||
members=securetar.secure_path(ostf),
|
||||
filter="fully_trusted",
|
||||
)
|
||||
backup_meta_file = Path(tempdir, "extracted", "backup.json")
|
||||
backup_meta = json.loads(backup_meta_file.read_text(encoding="utf8"))
|
||||
|
||||
if (
|
||||
backup_meta_version := AwesomeVersion(
|
||||
backup_meta["homeassistant"]["version"]
|
||||
)
|
||||
) > HA_VERSION:
|
||||
raise ValueError(
|
||||
f"You need at least Home Assistant version {backup_meta_version} to restore this backup"
|
||||
)
|
||||
|
||||
with securetar.SecureTarFile(
|
||||
Path(
|
||||
tempdir,
|
||||
"extracted",
|
||||
f"homeassistant.tar{'.gz' if backup_meta["compressed"] else ''}",
|
||||
),
|
||||
gzip=backup_meta["compressed"],
|
||||
mode="r",
|
||||
) as istf:
|
||||
for member in istf.getmembers():
|
||||
if member.name == "data":
|
||||
continue
|
||||
member.name = member.name.replace("data/", "")
|
||||
_clear_configuration_directory(config_dir)
|
||||
istf.extractall(
|
||||
path=config_dir,
|
||||
members=[
|
||||
member
|
||||
for member in securetar.secure_path(istf)
|
||||
if member.name != "data"
|
||||
],
|
||||
filter="fully_trusted",
|
||||
)
|
||||
|
||||
|
||||
def restore_backup(config_dir_path: str) -> bool:
|
||||
"""Restore the backup file if any.
|
||||
|
||||
Returns True if a restore backup file was found and restored, False otherwise.
|
||||
"""
|
||||
config_dir = Path(config_dir_path)
|
||||
if not (restore_content := restore_backup_file_content(config_dir)):
|
||||
return False
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
||||
backup_file_path = restore_content.backup_file_path
|
||||
_LOGGER.info("Restoring %s", backup_file_path)
|
||||
try:
|
||||
_extract_backup(config_dir, backup_file_path)
|
||||
except FileNotFoundError as err:
|
||||
raise ValueError(f"Backup file {backup_file_path} does not exist") from err
|
||||
_LOGGER.info("Restore complete, restarting")
|
||||
return True
|
@@ -70,6 +70,7 @@ from .const import (
|
||||
REQUIRED_NEXT_PYTHON_VER,
|
||||
SIGNAL_BOOTSTRAP_INTEGRATIONS,
|
||||
)
|
||||
from .core_config import async_process_ha_core_config
|
||||
from .exceptions import HomeAssistantError
|
||||
from .helpers import (
|
||||
area_registry,
|
||||
@@ -479,7 +480,7 @@ async def async_from_config_dict(
|
||||
core_config = config.get(core.DOMAIN, {})
|
||||
|
||||
try:
|
||||
await conf_util.async_process_ha_core_config(hass, core_config)
|
||||
await async_process_ha_core_config(hass, core_config)
|
||||
except vol.Invalid as config_err:
|
||||
conf_util.async_log_schema_error(config_err, core.DOMAIN, core_config, hass)
|
||||
async_notify_setup_error(hass, core.DOMAIN)
|
||||
|
@@ -5,7 +5,6 @@
|
||||
"google_assistant",
|
||||
"google_assistant_sdk",
|
||||
"google_cloud",
|
||||
"google_domains",
|
||||
"google_generative_ai_conversation",
|
||||
"google_mail",
|
||||
"google_maps",
|
||||
|
5
homeassistant/brands/husqvarna.json
Normal file
5
homeassistant/brands/husqvarna.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "husqvarna",
|
||||
"name": "Husqvarna",
|
||||
"integrations": ["husqvarna_automower", "husqvarna_automower_ble"]
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "lg",
|
||||
"name": "LG",
|
||||
"integrations": ["lg_netcast", "lg_soundbar", "webostv"]
|
||||
"integrations": ["lg_netcast", "lg_soundbar", "lg_thinq", "webostv"]
|
||||
}
|
||||
|
@@ -7,13 +7,9 @@ from jaraco.abode.devices.alarm import Alarm
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntity,
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
@@ -44,14 +40,14 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanelEntity):
|
||||
_device: Alarm
|
||||
|
||||
@property
|
||||
def state(self) -> str | None:
|
||||
def alarm_state(self) -> AlarmControlPanelState | None:
|
||||
"""Return the state of the device."""
|
||||
if self._device.is_standby:
|
||||
return STATE_ALARM_DISARMED
|
||||
return AlarmControlPanelState.DISARMED
|
||||
if self._device.is_away:
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
return AlarmControlPanelState.ARMED_AWAY
|
||||
if self._device.is_home:
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
return AlarmControlPanelState.ARMED_HOME
|
||||
return None
|
||||
|
||||
def alarm_disarm(self, code: str | None = None) -> None:
|
||||
|
@@ -102,15 +102,7 @@ class AbodeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
existing_entry = await self.async_set_unique_id(self._username)
|
||||
|
||||
if existing_entry:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry, data=config_data
|
||||
)
|
||||
# Reload the Abode config entry otherwise devices will remain unavailable
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
)
|
||||
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
return self.async_update_reload_and_abort(existing_entry, data=config_data)
|
||||
|
||||
return self.async_create_entry(
|
||||
title=cast(str, self._username), data=config_data
|
||||
|
@@ -2,13 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from accuweather import AccuWeather
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_PLATFORM
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -16,7 +14,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN, UPDATE_INTERVAL_DAILY_FORECAST, UPDATE_INTERVAL_OBSERVATION
|
||||
from .coordinator import (
|
||||
AccuWeatherConfigEntry,
|
||||
AccuWeatherDailyForecastDataUpdateCoordinator,
|
||||
AccuWeatherData,
|
||||
AccuWeatherObservationDataUpdateCoordinator,
|
||||
)
|
||||
|
||||
@@ -25,17 +25,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccuWeatherData:
|
||||
"""Data for AccuWeather integration."""
|
||||
|
||||
coordinator_observation: AccuWeatherObservationDataUpdateCoordinator
|
||||
coordinator_daily_forecast: AccuWeatherDailyForecastDataUpdateCoordinator
|
||||
|
||||
|
||||
type AccuWeatherConfigEntry = ConfigEntry[AccuWeatherData]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AccuWeatherConfigEntry) -> bool:
|
||||
"""Set up AccuWeather as config entry."""
|
||||
api_key: str = entry.data[CONF_API_KEY]
|
||||
@@ -50,6 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AccuWeatherConfigEntry)
|
||||
|
||||
coordinator_observation = AccuWeatherObservationDataUpdateCoordinator(
|
||||
hass,
|
||||
entry,
|
||||
accuweather,
|
||||
name,
|
||||
"observation",
|
||||
@@ -58,6 +48,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AccuWeatherConfigEntry)
|
||||
|
||||
coordinator_daily_forecast = AccuWeatherDailyForecastDataUpdateCoordinator(
|
||||
hass,
|
||||
entry,
|
||||
accuweather,
|
||||
name,
|
||||
"daily forecast",
|
||||
|
@@ -1,6 +1,9 @@
|
||||
"""The AccuWeather coordinator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -8,6 +11,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
@@ -23,6 +27,17 @@ EXCEPTIONS = (ApiError, ClientConnectorError, InvalidApiKeyError, RequestsExceed
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccuWeatherData:
|
||||
"""Data for AccuWeather integration."""
|
||||
|
||||
coordinator_observation: AccuWeatherObservationDataUpdateCoordinator
|
||||
coordinator_daily_forecast: AccuWeatherDailyForecastDataUpdateCoordinator
|
||||
|
||||
|
||||
type AccuWeatherConfigEntry = ConfigEntry[AccuWeatherData]
|
||||
|
||||
|
||||
class AccuWeatherObservationDataUpdateCoordinator(
|
||||
DataUpdateCoordinator[dict[str, Any]]
|
||||
):
|
||||
@@ -31,6 +46,7 @@ class AccuWeatherObservationDataUpdateCoordinator(
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: AccuWeatherConfigEntry,
|
||||
accuweather: AccuWeather,
|
||||
name: str,
|
||||
coordinator_type: str,
|
||||
@@ -48,6 +64,7 @@ class AccuWeatherObservationDataUpdateCoordinator(
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"{name} ({coordinator_type})",
|
||||
update_interval=update_interval,
|
||||
)
|
||||
@@ -73,6 +90,7 @@ class AccuWeatherDailyForecastDataUpdateCoordinator(
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: AccuWeatherConfigEntry,
|
||||
accuweather: AccuWeather,
|
||||
name: str,
|
||||
coordinator_type: str,
|
||||
@@ -90,6 +108,7 @@ class AccuWeatherDailyForecastDataUpdateCoordinator(
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"{name} ({coordinator_type})",
|
||||
update_interval=update_interval,
|
||||
)
|
||||
|
@@ -8,7 +8,7 @@ from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AccuWeatherConfigEntry, AccuWeatherData
|
||||
from .coordinator import AccuWeatherConfigEntry, AccuWeatherData
|
||||
|
||||
TO_REDACT = {CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE}
|
||||
|
||||
|
@@ -28,7 +28,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AccuWeatherConfigEntry
|
||||
from .const import (
|
||||
API_METRIC,
|
||||
ATTR_CATEGORY,
|
||||
@@ -41,6 +40,7 @@ from .const import (
|
||||
MAX_FORECAST_DAYS,
|
||||
)
|
||||
from .coordinator import (
|
||||
AccuWeatherConfigEntry,
|
||||
AccuWeatherDailyForecastDataUpdateCoordinator,
|
||||
AccuWeatherObservationDataUpdateCoordinator,
|
||||
)
|
||||
|
@@ -9,8 +9,8 @@ from accuweather.const import ENDPOINT
|
||||
from homeassistant.components import system_health
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from . import AccuWeatherConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AccuWeatherConfigEntry
|
||||
|
||||
|
||||
@callback
|
||||
|
@@ -33,7 +33,6 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
|
||||
from . import AccuWeatherConfigEntry, AccuWeatherData
|
||||
from .const import (
|
||||
API_METRIC,
|
||||
ATTR_DIRECTION,
|
||||
@@ -43,7 +42,9 @@ from .const import (
|
||||
CONDITION_MAP,
|
||||
)
|
||||
from .coordinator import (
|
||||
AccuWeatherConfigEntry,
|
||||
AccuWeatherDailyForecastDataUpdateCoordinator,
|
||||
AccuWeatherData,
|
||||
AccuWeatherObservationDataUpdateCoordinator,
|
||||
)
|
||||
|
||||
|
@@ -7,7 +7,6 @@ from typing import Any
|
||||
from adguardhome import AdGuardHome, AdGuardHomeConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.hassio import HassioServiceInfo
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
@@ -18,6 +17,7 @@ from homeassistant.const import (
|
||||
CONF_VERIFY_SSL,
|
||||
)
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
@@ -55,6 +55,7 @@ async def async_setup_entry(
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=entry,
|
||||
name="Advantage Air",
|
||||
update_method=async_get,
|
||||
update_interval=timedelta(seconds=ADVANTAGE_AIR_SYNC_INTERVAL),
|
||||
|
@@ -1,6 +1,5 @@
|
||||
"""The AEMET OpenData component."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from aemet_opendata.exceptions import AemetError, TownNotFound
|
||||
@@ -13,20 +12,10 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
from .const import CONF_STATION_UPDATES, PLATFORMS
|
||||
from .coordinator import WeatherUpdateCoordinator
|
||||
from .coordinator import AemetConfigEntry, AemetData, WeatherUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type AemetConfigEntry = ConfigEntry[AemetData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AemetData:
|
||||
"""Aemet runtime data."""
|
||||
|
||||
name: str
|
||||
coordinator: WeatherUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AemetConfigEntry) -> bool:
|
||||
"""Set up AEMET OpenData as config entry."""
|
||||
@@ -46,7 +35,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AemetConfigEntry) -> boo
|
||||
except AemetError as err:
|
||||
raise ConfigEntryNotReady(err) from err
|
||||
|
||||
weather_coordinator = WeatherUpdateCoordinator(hass, aemet)
|
||||
weather_coordinator = WeatherUpdateCoordinator(hass, entry, aemet)
|
||||
await weather_coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = AemetData(name=name, coordinator=weather_coordinator)
|
||||
|
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any, Final, cast
|
||||
@@ -19,6 +20,7 @@ from aemet_opendata.helpers import dict_nested_value
|
||||
from aemet_opendata.interface import AEMET
|
||||
|
||||
from homeassistant.components.weather import Forecast
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
@@ -29,6 +31,16 @@ _LOGGER = logging.getLogger(__name__)
|
||||
API_TIMEOUT: Final[int] = 120
|
||||
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
|
||||
|
||||
type AemetConfigEntry = ConfigEntry[AemetData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AemetData:
|
||||
"""Aemet runtime data."""
|
||||
|
||||
name: str
|
||||
coordinator: WeatherUpdateCoordinator
|
||||
|
||||
|
||||
class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Weather data update coordinator."""
|
||||
@@ -36,6 +48,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: AemetConfigEntry,
|
||||
aemet: AEMET,
|
||||
) -> None:
|
||||
"""Initialize coordinator."""
|
||||
@@ -44,6 +57,7 @@ class WeatherUpdateCoordinator(DataUpdateCoordinator):
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=entry,
|
||||
name=DOMAIN,
|
||||
update_interval=WEATHER_UPDATE_INTERVAL,
|
||||
)
|
||||
|
@@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AemetConfigEntry
|
||||
from .coordinator import AemetConfigEntry
|
||||
|
||||
TO_REDACT_CONFIG = [
|
||||
CONF_API_KEY,
|
||||
|
@@ -55,7 +55,6 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import AemetConfigEntry
|
||||
from .const import (
|
||||
ATTR_API_CONDITION,
|
||||
ATTR_API_FORECAST_CONDITION,
|
||||
@@ -87,7 +86,7 @@ from .const import (
|
||||
ATTR_API_WIND_SPEED,
|
||||
CONDITIONS_MAP,
|
||||
)
|
||||
from .coordinator import WeatherUpdateCoordinator
|
||||
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
||||
from .entity import AemetEntity
|
||||
|
||||
|
||||
@@ -249,6 +248,7 @@ WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
|
||||
name="Rain",
|
||||
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AemetSensorEntityDescription(
|
||||
key=ATTR_API_RAIN_PROB,
|
||||
@@ -263,6 +263,7 @@ WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
|
||||
name="Snow",
|
||||
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AemetSensorEntityDescription(
|
||||
key=ATTR_API_SNOW_PROB,
|
||||
|
@@ -27,9 +27,8 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AemetConfigEntry
|
||||
from .const import CONDITIONS_MAP
|
||||
from .coordinator import WeatherUpdateCoordinator
|
||||
from .coordinator import AemetConfigEntry, WeatherUpdateCoordinator
|
||||
from .entity import AemetEntity
|
||||
|
||||
|
||||
|
@@ -5,12 +5,7 @@ from __future__ import annotations
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntity,
|
||||
AlarmControlPanelEntityFeature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -65,37 +60,37 @@ class AgentBaseStation(AlarmControlPanelEntity):
|
||||
self._attr_available = self._client.is_available
|
||||
armed = self._client.is_armed
|
||||
if armed is None:
|
||||
self._attr_state = None
|
||||
self._attr_alarm_state = None
|
||||
return
|
||||
if armed:
|
||||
prof = (await self._client.get_active_profile()).lower()
|
||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_AWAY
|
||||
if prof == CONF_HOME_MODE_NAME:
|
||||
self._attr_state = STATE_ALARM_ARMED_HOME
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_HOME
|
||||
elif prof == CONF_NIGHT_MODE_NAME:
|
||||
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_NIGHT
|
||||
else:
|
||||
self._attr_state = STATE_ALARM_DISARMED
|
||||
self._attr_alarm_state = AlarmControlPanelState.DISARMED
|
||||
|
||||
async def async_alarm_disarm(self, code: str | None = None) -> None:
|
||||
"""Send disarm command."""
|
||||
await self._client.disarm()
|
||||
self._attr_state = STATE_ALARM_DISARMED
|
||||
self._attr_alarm_state = AlarmControlPanelState.DISARMED
|
||||
|
||||
async def async_alarm_arm_away(self, code: str | None = None) -> None:
|
||||
"""Send arm away command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_AWAY_MODE_NAME)
|
||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_AWAY
|
||||
|
||||
async def async_alarm_arm_home(self, code: str | None = None) -> None:
|
||||
"""Send arm home command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_HOME_MODE_NAME)
|
||||
self._attr_state = STATE_ALARM_ARMED_HOME
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_HOME
|
||||
|
||||
async def async_alarm_arm_night(self, code: str | None = None) -> None:
|
||||
"""Send arm night command. Uses custom mode."""
|
||||
await self._client.arm()
|
||||
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)
|
||||
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_NIGHT
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/agent_dvr",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["agent"],
|
||||
"requirements": ["agent-py==0.0.23"]
|
||||
"requirements": ["agent-py==0.0.24"]
|
||||
}
|
||||
|
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airgradient",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airgradient==0.9.0"],
|
||||
"requirements": ["airgradient==0.9.1"],
|
||||
"zeroconf": ["_airgradient._tcp.local."]
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
"""Airgradient Update platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from functools import cached_property
|
||||
|
||||
from propcache import cached_property
|
||||
|
||||
from homeassistant.components.update import UpdateDeviceClass, UpdateEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
@@ -1,5 +1,7 @@
|
||||
"""Config flow for AirNow integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -12,7 +14,6 @@ from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithConfigEntry,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -120,12 +121,12 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> OptionsFlow:
|
||||
) -> AirNowOptionsFlowHandler:
|
||||
"""Return the options flow."""
|
||||
return AirNowOptionsFlowHandler(config_entry)
|
||||
return AirNowOptionsFlowHandler()
|
||||
|
||||
|
||||
class AirNowOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
class AirNowOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle an options flow for AirNow."""
|
||||
|
||||
async def async_step_init(
|
||||
@@ -136,12 +137,7 @@ class AirNowOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
return self.async_create_entry(data=user_input)
|
||||
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_RADIUS): vol.All(
|
||||
int,
|
||||
vol.Range(min=5),
|
||||
),
|
||||
}
|
||||
{vol.Optional(CONF_RADIUS): vol.All(int, vol.Range(min=5))}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
|
@@ -42,6 +42,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) ->
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=entry,
|
||||
name=DOMAIN,
|
||||
update_method=_update_method,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
|
@@ -2,75 +2,27 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
|
||||
from bleak_retry_connector import close_stale_connections_by_address
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, MAX_RETRIES_AFTER_STARTUP
|
||||
from .const import MAX_RETRIES_AFTER_STARTUP
|
||||
from .coordinator import AirthingsBLEConfigEntry, AirthingsBLEDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AirthingsBLEDataUpdateCoordinator = DataUpdateCoordinator[AirthingsDevice]
|
||||
AirthingsBLEConfigEntry = ConfigEntry[AirthingsBLEDataUpdateCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: AirthingsBLEConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Airthings BLE device from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
address = entry.unique_id
|
||||
|
||||
is_metric = hass.config.units is METRIC_SYSTEM
|
||||
assert address is not None
|
||||
|
||||
await close_stale_connections_by_address(address)
|
||||
|
||||
ble_device = bluetooth.async_ble_device_from_address(hass, address)
|
||||
|
||||
if not ble_device:
|
||||
raise ConfigEntryNotReady(
|
||||
f"Could not find Airthings device with address {address}"
|
||||
)
|
||||
|
||||
airthings = AirthingsBluetoothDeviceData(_LOGGER, is_metric)
|
||||
|
||||
async def _async_update_method() -> AirthingsDevice:
|
||||
"""Get data from Airthings BLE."""
|
||||
try:
|
||||
data = await airthings.update_device(ble_device)
|
||||
except Exception as err:
|
||||
raise UpdateFailed(f"Unable to fetch data: {err}") from err
|
||||
|
||||
return data
|
||||
|
||||
coordinator: AirthingsBLEDataUpdateCoordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=_async_update_method,
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
)
|
||||
|
||||
coordinator = AirthingsBLEDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
# Once its setup and we know we are not going to delay
|
||||
# the startup of Home Assistant, we can set the max attempts
|
||||
# to a higher value. If the first connection attempt fails,
|
||||
# Home Assistant's built-in retry logic will take over.
|
||||
airthings.set_max_attempts(MAX_RETRIES_AFTER_STARTUP)
|
||||
coordinator.airthings.set_max_attempts(MAX_RETRIES_AFTER_STARTUP)
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
|
68
homeassistant/components/airthings_ble/coordinator.py
Normal file
68
homeassistant/components/airthings_ble/coordinator.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""The Airthings BLE integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
|
||||
from bleak.backends.device import BLEDevice
|
||||
from bleak_retry_connector import close_stale_connections_by_address
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type AirthingsBLEConfigEntry = ConfigEntry[AirthingsBLEDataUpdateCoordinator]
|
||||
|
||||
|
||||
class AirthingsBLEDataUpdateCoordinator(DataUpdateCoordinator[AirthingsDevice]):
|
||||
"""Class to manage fetching Airthings BLE data."""
|
||||
|
||||
ble_device: BLEDevice
|
||||
config_entry: AirthingsBLEConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: AirthingsBLEConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
self.airthings = AirthingsBluetoothDeviceData(
|
||||
_LOGGER, hass.config.units is METRIC_SYSTEM
|
||||
)
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=entry,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
||||
)
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
address = self.config_entry.unique_id
|
||||
|
||||
assert address is not None
|
||||
|
||||
await close_stale_connections_by_address(address)
|
||||
|
||||
ble_device = bluetooth.async_ble_device_from_address(self.hass, address)
|
||||
|
||||
if not ble_device:
|
||||
raise ConfigEntryNotReady(
|
||||
f"Could not find Airthings device with address {address}"
|
||||
)
|
||||
self.ble_device = ble_device
|
||||
|
||||
async def _async_update_data(self) -> AirthingsDevice:
|
||||
"""Get data from Airthings BLE."""
|
||||
try:
|
||||
data = await self.airthings.update_device(self.ble_device)
|
||||
except Exception as err:
|
||||
raise UpdateFailed(f"Unable to fetch data: {err}") from err
|
||||
|
||||
return data
|
@@ -24,5 +24,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["airthings-ble==0.9.1"]
|
||||
"requirements": ["airthings-ble==0.9.2"]
|
||||
}
|
||||
|
@@ -34,8 +34,8 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from . import AirthingsBLEConfigEntry, AirthingsBLEDataUpdateCoordinator
|
||||
from .const import DOMAIN, VOLUME_BECQUEREL, VOLUME_PICOCURIE
|
||||
from .coordinator import AirthingsBLEConfigEntry, AirthingsBLEDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@@ -9,8 +9,6 @@ from homeassistant.const import CONF_HOST, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.COVER]
|
||||
|
||||
type Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]
|
||||
@@ -19,8 +17,6 @@ type Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: Airtouch5ConfigEntry) -> bool:
|
||||
"""Set up Airtouch 5 from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
# Create API instance
|
||||
host = entry.data[CONF_HOST]
|
||||
client = Airtouch5SimpleClient(host)
|
||||
|
@@ -204,6 +204,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) ->
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
config_entry=entry,
|
||||
name=async_get_geography_id(entry.data),
|
||||
# We give a placeholder update interval in order to create the coordinator;
|
||||
# then, below, we use the coordinator's presence (along with any other
|
||||
|
@@ -16,7 +16,12 @@ from pyairvisual.cloud_api import (
|
||||
from pyairvisual.errors import AirVisualError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_REAUTH,
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_COUNTRY,
|
||||
@@ -140,12 +145,11 @@ class AirVisualFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
valid_keys.add(user_input[CONF_API_KEY])
|
||||
|
||||
if existing_entry := await self.async_set_unique_id(self._geo_id):
|
||||
self.hass.config_entries.async_update_entry(existing_entry, data=user_input)
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
if self.source == SOURCE_REAUTH:
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(),
|
||||
data_updates={CONF_API_KEY: user_input[CONF_API_KEY]},
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_create_entry(
|
||||
title=f"Cloud API ({self._geo_id})",
|
||||
|
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"general_error": "[%key:common::config_flow::error::unknown%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
||||
"location_not_found": "Location not found",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
|
@@ -81,6 +81,7 @@ async def async_setup_entry(
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
config_entry=entry,
|
||||
name="Node/Pro data",
|
||||
update_interval=UPDATE_INTERVAL,
|
||||
update_method=async_get_data,
|
||||
|
@@ -14,7 +14,7 @@ from pyairvisual.node import (
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
@@ -76,9 +76,7 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self._reauth_entry: ConfigEntry | None = None
|
||||
_reauth_entry_data: Mapping[str, Any]
|
||||
|
||||
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Import a config entry from `airvisual` integration (see #83882)."""
|
||||
@@ -88,9 +86,7 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle configuration by re-auth."""
|
||||
self._reauth_entry = self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
)
|
||||
self._reauth_entry_data = entry_data
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
@@ -102,10 +98,8 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
step_id="reauth_confirm", data_schema=STEP_REAUTH_SCHEMA
|
||||
)
|
||||
|
||||
assert self._reauth_entry
|
||||
|
||||
validation_result = await async_validate_credentials(
|
||||
self._reauth_entry.data[CONF_IP_ADDRESS], user_input[CONF_PASSWORD]
|
||||
self._reauth_entry_data[CONF_IP_ADDRESS], user_input[CONF_PASSWORD]
|
||||
)
|
||||
|
||||
if validation_result.errors:
|
||||
@@ -115,13 +109,9 @@ class AirVisualProFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
errors=validation_result.errors,
|
||||
)
|
||||
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._reauth_entry, data=self._reauth_entry.data | user_input
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reauth_entry(), data_updates=user_input
|
||||
)
|
||||
self.hass.async_create_task(
|
||||
self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
|
@@ -24,6 +24,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.CLIMATE,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.WATER_HEATER,
|
||||
]
|
||||
|
||||
|
@@ -85,6 +85,7 @@ HVAC_MODE_LIB_TO_HASS: Final[dict[OperationMode, HVACMode]] = {
|
||||
OperationMode.HEATING: HVACMode.HEAT,
|
||||
OperationMode.FAN: HVACMode.FAN_ONLY,
|
||||
OperationMode.DRY: HVACMode.DRY,
|
||||
OperationMode.AUX_HEATING: HVACMode.HEAT,
|
||||
OperationMode.AUTO: HVACMode.HEAT_COOL,
|
||||
}
|
||||
HVAC_MODE_HASS_TO_LIB: Final[dict[HVACMode, OperationMode]] = {
|
||||
@@ -157,9 +158,10 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
self._attr_temperature_unit = TEMP_UNIT_LIB_TO_HASS[
|
||||
self.get_airzone_value(AZD_TEMP_UNIT)
|
||||
]
|
||||
self._attr_hvac_modes = [
|
||||
_attr_hvac_modes = [
|
||||
HVAC_MODE_LIB_TO_HASS[mode] for mode in self.get_airzone_value(AZD_MODES)
|
||||
]
|
||||
self._attr_hvac_modes = list(dict.fromkeys(_attr_hvac_modes))
|
||||
if (
|
||||
self.get_airzone_value(AZD_SPEED) is not None
|
||||
and self.get_airzone_value(AZD_SPEEDS) is not None
|
||||
@@ -273,12 +275,18 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
self._attr_min_temp = self.get_airzone_value(AZD_TEMP_MIN)
|
||||
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
||||
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
if (
|
||||
self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
and self._attr_hvac_mode == HVACMode.HEAT_COOL
|
||||
):
|
||||
self._attr_target_temperature_high = self.get_airzone_value(
|
||||
AZD_COOL_TEMP_SET
|
||||
)
|
||||
self._attr_target_temperature_low = self.get_airzone_value(
|
||||
AZD_HEAT_TEMP_SET
|
||||
)
|
||||
self._attr_target_temperature = None
|
||||
else:
|
||||
self._attr_target_temperature_high = None
|
||||
self._attr_target_temperature_low = None
|
||||
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
|
||||
|
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.9.3"]
|
||||
"requirements": ["aioairzone==0.9.5"]
|
||||
}
|
||||
|
122
homeassistant/components/airzone/switch.py
Normal file
122
homeassistant/components/airzone/switch.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""Support for the Airzone switch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Final
|
||||
|
||||
from aioairzone.const import API_ON, AZD_ON, AZD_ZONES
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AirzoneConfigEntry
|
||||
from .coordinator import AirzoneUpdateCoordinator
|
||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AirzoneSwitchDescription(SwitchEntityDescription):
|
||||
"""Class to describe an Airzone switch entity."""
|
||||
|
||||
api_param: str
|
||||
|
||||
|
||||
ZONE_SWITCH_TYPES: Final[tuple[AirzoneSwitchDescription, ...]] = (
|
||||
AirzoneSwitchDescription(
|
||||
api_param=API_ON,
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
key=AZD_ON,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AirzoneConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone switch from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
added_zones: set[str] = set()
|
||||
|
||||
def _async_entity_listener() -> None:
|
||||
"""Handle additions of switch."""
|
||||
|
||||
zones_data = coordinator.data.get(AZD_ZONES, {})
|
||||
received_zones = set(zones_data)
|
||||
new_zones = received_zones - added_zones
|
||||
if new_zones:
|
||||
async_add_entities(
|
||||
AirzoneZoneSwitch(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
system_zone_id,
|
||||
zones_data.get(system_zone_id),
|
||||
)
|
||||
for system_zone_id in new_zones
|
||||
for description in ZONE_SWITCH_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 AirzoneBaseSwitch(AirzoneEntity, SwitchEntity):
|
||||
"""Define an Airzone switch."""
|
||||
|
||||
entity_description: AirzoneSwitchDescription
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
self._async_update_attrs()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update switch attributes."""
|
||||
self._attr_is_on = self.get_airzone_value(self.entity_description.key)
|
||||
|
||||
|
||||
class AirzoneZoneSwitch(AirzoneZoneEntity, AirzoneBaseSwitch):
|
||||
"""Define an Airzone Zone switch."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: AirzoneSwitchDescription,
|
||||
entry: ConfigEntry,
|
||||
system_zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, system_zone_id, zone_data)
|
||||
|
||||
self._attr_name = None
|
||||
self._attr_unique_id = (
|
||||
f"{self._attr_unique_id}_{system_zone_id}_{description.key}"
|
||||
)
|
||||
self.entity_description = description
|
||||
|
||||
self._async_update_attrs()
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
param = self.entity_description.api_param
|
||||
await self._async_update_hvac_params({param: True})
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
param = self.entity_description.api_param
|
||||
await self._async_update_hvac_params({param: False})
|
@@ -17,6 +17,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.CLIMATE,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.WATER_HEATER,
|
||||
]
|
||||
|
||||
|
@@ -224,14 +224,20 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_max_temp = self.get_airzone_value(AZD_TEMP_SET_MAX)
|
||||
self._attr_min_temp = self.get_airzone_value(AZD_TEMP_SET_MIN)
|
||||
if self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
if (
|
||||
self.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
and self._attr_hvac_mode == HVACMode.HEAT_COOL
|
||||
):
|
||||
self._attr_target_temperature_high = self.get_airzone_value(
|
||||
AZD_TEMP_SET_COOL_AIR
|
||||
)
|
||||
self._attr_target_temperature_low = self.get_airzone_value(
|
||||
AZD_TEMP_SET_HOT_AIR
|
||||
)
|
||||
self._attr_target_temperature = None
|
||||
else:
|
||||
self._attr_target_temperature_high = None
|
||||
self._attr_target_temperature_low = None
|
||||
self._attr_target_temperature = self.get_airzone_value(AZD_TEMP_SET)
|
||||
|
||||
|
||||
@@ -304,6 +310,10 @@ class AirzoneDeviceClimate(AirzoneClimate):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
if hvac_mode is not None:
|
||||
await self.async_set_hvac_mode(hvac_mode)
|
||||
|
||||
params: dict[str, Any] = {}
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
params[API_SETPOINT] = {
|
||||
@@ -327,9 +337,6 @@ class AirzoneDeviceClimate(AirzoneClimate):
|
||||
}
|
||||
await self._async_update_params(params)
|
||||
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
await self.async_set_hvac_mode(kwargs[ATTR_HVAC_MODE])
|
||||
|
||||
|
||||
class AirzoneDeviceGroupClimate(AirzoneClimate):
|
||||
"""Define an Airzone Cloud DeviceGroup base class."""
|
||||
@@ -360,6 +367,10 @@ class AirzoneDeviceGroupClimate(AirzoneClimate):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
if hvac_mode is not None:
|
||||
await self.async_set_hvac_mode(hvac_mode)
|
||||
|
||||
params: dict[str, Any] = {}
|
||||
if ATTR_TEMPERATURE in kwargs:
|
||||
params[API_PARAMS] = {
|
||||
@@ -370,9 +381,6 @@ class AirzoneDeviceGroupClimate(AirzoneClimate):
|
||||
}
|
||||
await self._async_update_params(params)
|
||||
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
await self.async_set_hvac_mode(kwargs[ATTR_HVAC_MODE])
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set hvac mode."""
|
||||
params: dict[str, Any] = {
|
||||
|
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.6.5"]
|
||||
"requirements": ["aioairzone-cloud==0.6.10"]
|
||||
}
|
||||
|
@@ -2,14 +2,19 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Final
|
||||
|
||||
from aioairzone_cloud.common import AirQualityMode
|
||||
from aioairzone_cloud.common import AirQualityMode, OperationMode
|
||||
from aioairzone_cloud.const import (
|
||||
API_AQ_MODE_CONF,
|
||||
API_MODE,
|
||||
API_VALUE,
|
||||
AZD_AQ_MODE_CONF,
|
||||
AZD_MASTER,
|
||||
AZD_MODE,
|
||||
AZD_MODES,
|
||||
AZD_ZONES,
|
||||
)
|
||||
|
||||
@@ -28,7 +33,10 @@ class AirzoneSelectDescription(SelectEntityDescription):
|
||||
"""Class to describe an Airzone select entity."""
|
||||
|
||||
api_param: str
|
||||
options_dict: dict[str, str]
|
||||
options_dict: dict[str, Any]
|
||||
options_fn: Callable[[dict[str, Any], dict[str, Any]], list[str]] = (
|
||||
lambda zone_data, value: list(value)
|
||||
)
|
||||
|
||||
|
||||
AIR_QUALITY_MAP: Final[dict[str, str]] = {
|
||||
@@ -37,6 +45,35 @@ AIR_QUALITY_MAP: Final[dict[str, str]] = {
|
||||
"auto": AirQualityMode.AUTO,
|
||||
}
|
||||
|
||||
MODE_MAP: Final[dict[str, int]] = {
|
||||
"cool": OperationMode.COOLING,
|
||||
"dry": OperationMode.DRY,
|
||||
"fan": OperationMode.VENTILATION,
|
||||
"heat": OperationMode.HEATING,
|
||||
"heat_cool": OperationMode.AUTO,
|
||||
"stop": OperationMode.STOP,
|
||||
}
|
||||
|
||||
|
||||
def main_zone_options(
|
||||
zone_data: dict[str, Any],
|
||||
options: dict[str, int],
|
||||
) -> list[str]:
|
||||
"""Filter available modes."""
|
||||
modes = zone_data.get(AZD_MODES, [])
|
||||
return [k for k, v in options.items() if v in modes]
|
||||
|
||||
|
||||
MAIN_ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
||||
AirzoneSelectDescription(
|
||||
api_param=API_MODE,
|
||||
key=AZD_MODE,
|
||||
options_dict=MODE_MAP,
|
||||
options_fn=main_zone_options,
|
||||
translation_key="modes",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
ZONE_SELECT_TYPES: Final[tuple[AirzoneSelectDescription, ...]] = (
|
||||
AirzoneSelectDescription(
|
||||
@@ -59,7 +96,19 @@ async def async_setup_entry(
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
# Zones
|
||||
async_add_entities(
|
||||
entities: list[AirzoneZoneSelect] = [
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for description in MAIN_ZONE_SELECT_TYPES
|
||||
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items()
|
||||
if description.key in zone_data and zone_data.get(AZD_MASTER)
|
||||
]
|
||||
|
||||
entities.extend(
|
||||
AirzoneZoneSelect(
|
||||
coordinator,
|
||||
description,
|
||||
@@ -71,6 +120,8 @@ async def async_setup_entry(
|
||||
if description.key in zone_data
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class AirzoneBaseSelect(AirzoneEntity, SelectEntity):
|
||||
"""Define an Airzone Cloud select."""
|
||||
@@ -110,6 +161,11 @@ class AirzoneZoneSelect(AirzoneZoneEntity, AirzoneBaseSelect):
|
||||
|
||||
self._attr_unique_id = f"{zone_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
self._attr_options = self.entity_description.options_fn(
|
||||
zone_data, description.options_dict
|
||||
)
|
||||
|
||||
self.values_dict = {v: k for k, v in description.options_dict.items()}
|
||||
|
||||
self._async_update_attrs()
|
||||
|
@@ -36,6 +36,17 @@
|
||||
"on": "On",
|
||||
"auto": "Auto"
|
||||
}
|
||||
},
|
||||
"modes": {
|
||||
"name": "Mode",
|
||||
"state": {
|
||||
"cool": "[%key:component::climate::entity_component::_::state::cool%]",
|
||||
"dry": "[%key:component::climate::entity_component::_::state::dry%]",
|
||||
"fan": "[%key:component::climate::entity_component::_::state::fan_only%]",
|
||||
"heat": "[%key:component::climate::entity_component::_::state::heat%]",
|
||||
"heat_cool": "[%key:component::climate::entity_component::_::state::heat_cool%]",
|
||||
"stop": "Stop"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
115
homeassistant/components/airzone_cloud/switch.py
Normal file
115
homeassistant/components/airzone_cloud/switch.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""Support for the Airzone Cloud switch."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Final
|
||||
|
||||
from aioairzone_cloud.const import API_POWER, API_VALUE, AZD_POWER, AZD_ZONES
|
||||
|
||||
from homeassistant.components.switch import (
|
||||
SwitchDeviceClass,
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AirzoneCloudConfigEntry
|
||||
from .coordinator import AirzoneUpdateCoordinator
|
||||
from .entity import AirzoneEntity, AirzoneZoneEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AirzoneSwitchDescription(SwitchEntityDescription):
|
||||
"""Class to describe an Airzone switch entity."""
|
||||
|
||||
api_param: str
|
||||
|
||||
|
||||
ZONE_SWITCH_TYPES: Final[tuple[AirzoneSwitchDescription, ...]] = (
|
||||
AirzoneSwitchDescription(
|
||||
api_param=API_POWER,
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
key=AZD_POWER,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AirzoneCloudConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Airzone Cloud switch from a config_entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
# Zones
|
||||
async_add_entities(
|
||||
AirzoneZoneSwitch(
|
||||
coordinator,
|
||||
description,
|
||||
zone_id,
|
||||
zone_data,
|
||||
)
|
||||
for description in ZONE_SWITCH_TYPES
|
||||
for zone_id, zone_data in coordinator.data.get(AZD_ZONES, {}).items()
|
||||
if description.key in zone_data
|
||||
)
|
||||
|
||||
|
||||
class AirzoneBaseSwitch(AirzoneEntity, SwitchEntity):
|
||||
"""Define an Airzone Cloud switch."""
|
||||
|
||||
entity_description: AirzoneSwitchDescription
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
self._async_update_attrs()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update switch attributes."""
|
||||
self._attr_is_on = self.get_airzone_value(self.entity_description.key)
|
||||
|
||||
|
||||
class AirzoneZoneSwitch(AirzoneZoneEntity, AirzoneBaseSwitch):
|
||||
"""Define an Airzone Cloud Zone switch."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: AirzoneSwitchDescription,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, zone_id, zone_data)
|
||||
|
||||
self._attr_name = None
|
||||
self._attr_unique_id = f"{zone_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
self._async_update_attrs()
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity on."""
|
||||
param = self.entity_description.api_param
|
||||
params: dict[str, Any] = {
|
||||
param: {
|
||||
API_VALUE: True,
|
||||
}
|
||||
}
|
||||
await self._async_update_params(params)
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the entity off."""
|
||||
param = self.entity_description.api_param
|
||||
params: dict[str, Any] = {
|
||||
param: {
|
||||
API_VALUE: False,
|
||||
}
|
||||
}
|
||||
await self._async_update_params(params)
|
@@ -2,11 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
from functools import cached_property, partial
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Any, Final, final
|
||||
|
||||
from propcache import cached_property
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -32,6 +34,7 @@ from homeassistant.helpers.deprecation import (
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
@@ -48,6 +51,7 @@ from .const import ( # noqa: F401
|
||||
ATTR_CODE_ARM_REQUIRED,
|
||||
DOMAIN,
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
CodeFormat,
|
||||
)
|
||||
|
||||
@@ -141,6 +145,7 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||
"changed_by",
|
||||
"code_arm_required",
|
||||
"supported_features",
|
||||
"alarm_state",
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +153,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
"""An abstract class for alarm control entities."""
|
||||
|
||||
entity_description: AlarmControlPanelEntityDescription
|
||||
_attr_alarm_state: AlarmControlPanelState | None = None
|
||||
_attr_changed_by: str | None = None
|
||||
_attr_code_arm_required: bool = True
|
||||
_attr_code_format: CodeFormat | None = None
|
||||
@@ -156,6 +162,78 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
)
|
||||
_alarm_control_panel_option_default_code: str | None = None
|
||||
|
||||
__alarm_legacy_state: bool = False
|
||||
__alarm_legacy_state_reported: bool = False
|
||||
|
||||
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||
"""Post initialisation processing."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
if any(method in cls.__dict__ for method in ("_attr_state", "state")):
|
||||
# Integrations should use the 'alarm_state' property instead of
|
||||
# setting the state directly.
|
||||
cls.__alarm_legacy_state = True
|
||||
|
||||
def __setattr__(self, __name: str, __value: Any) -> None:
|
||||
"""Set attribute.
|
||||
|
||||
Deprecation warning if setting '_attr_state' directly
|
||||
unless already reported.
|
||||
"""
|
||||
if __name == "_attr_state":
|
||||
if self.__alarm_legacy_state_reported is not True:
|
||||
self._report_deprecated_alarm_state_handling()
|
||||
self.__alarm_legacy_state_reported = True
|
||||
return super().__setattr__(__name, __value)
|
||||
|
||||
@callback
|
||||
def add_to_platform_start(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
platform: EntityPlatform,
|
||||
parallel_updates: asyncio.Semaphore | None,
|
||||
) -> None:
|
||||
"""Start adding an entity to a platform."""
|
||||
super().add_to_platform_start(hass, platform, parallel_updates)
|
||||
if self.__alarm_legacy_state and not self.__alarm_legacy_state_reported:
|
||||
self._report_deprecated_alarm_state_handling()
|
||||
|
||||
@callback
|
||||
def _report_deprecated_alarm_state_handling(self) -> None:
|
||||
"""Report on deprecated handling of alarm state.
|
||||
|
||||
Integrations should implement alarm_state instead of using state directly.
|
||||
"""
|
||||
self.__alarm_legacy_state_reported = True
|
||||
if "custom_components" in type(self).__module__:
|
||||
# Do not report on core integrations as they have been fixed.
|
||||
report_issue = "report it to the custom integration author."
|
||||
_LOGGER.warning(
|
||||
"Entity %s (%s) is setting state directly"
|
||||
" which will stop working in HA Core 2025.11."
|
||||
" Entities should implement the 'alarm_state' property and"
|
||||
" return its state using the AlarmControlPanelState enum, please %s",
|
||||
self.entity_id,
|
||||
type(self),
|
||||
report_issue,
|
||||
)
|
||||
|
||||
@final
|
||||
@property
|
||||
def state(self) -> str | None:
|
||||
"""Return the current state."""
|
||||
if (alarm_state := self.alarm_state) is None:
|
||||
return None
|
||||
return alarm_state
|
||||
|
||||
@cached_property
|
||||
def alarm_state(self) -> AlarmControlPanelState | None:
|
||||
"""Return the current alarm control panel entity state.
|
||||
|
||||
Integrations should overwrite this or use the '_attr_alarm_state'
|
||||
attribute to set the alarm status using the 'AlarmControlPanelState' enum.
|
||||
"""
|
||||
return self._attr_alarm_state
|
||||
|
||||
@final
|
||||
@callback
|
||||
def code_or_default_code(self, code: str | None) -> str | None:
|
||||
|
@@ -17,6 +17,21 @@ ATTR_CHANGED_BY: Final = "changed_by"
|
||||
ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required"
|
||||
|
||||
|
||||
class AlarmControlPanelState(StrEnum):
|
||||
"""Alarm control panel entity states."""
|
||||
|
||||
DISARMED = "disarmed"
|
||||
ARMED_HOME = "armed_home"
|
||||
ARMED_AWAY = "armed_away"
|
||||
ARMED_NIGHT = "armed_night"
|
||||
ARMED_VACATION = "armed_vacation"
|
||||
ARMED_CUSTOM_BYPASS = "armed_custom_bypass"
|
||||
PENDING = "pending"
|
||||
ARMING = "arming"
|
||||
DISARMING = "disarming"
|
||||
TRIGGERED = "triggered"
|
||||
|
||||
|
||||
class CodeFormat(StrEnum):
|
||||
"""Code formats for the Alarm Control Panel."""
|
||||
|
||||
|
@@ -13,13 +13,6 @@ from homeassistant.const import (
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_TYPE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_VACATION,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
@@ -31,7 +24,7 @@ from homeassistant.helpers.config_validation import DEVICE_CONDITION_BASE_SCHEMA
|
||||
from homeassistant.helpers.entity import get_supported_features
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
|
||||
from . import DOMAIN
|
||||
from . import DOMAIN, AlarmControlPanelState
|
||||
from .const import (
|
||||
CONDITION_ARMED_AWAY,
|
||||
CONDITION_ARMED_CUSTOM_BYPASS,
|
||||
@@ -109,19 +102,19 @@ def async_condition_from_config(
|
||||
) -> condition.ConditionCheckerType:
|
||||
"""Create a function to test a device condition."""
|
||||
if config[CONF_TYPE] == CONDITION_TRIGGERED:
|
||||
state = STATE_ALARM_TRIGGERED
|
||||
state = AlarmControlPanelState.TRIGGERED
|
||||
elif config[CONF_TYPE] == CONDITION_DISARMED:
|
||||
state = STATE_ALARM_DISARMED
|
||||
state = AlarmControlPanelState.DISARMED
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_HOME:
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
state = AlarmControlPanelState.ARMED_HOME
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_AWAY:
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
state = AlarmControlPanelState.ARMED_AWAY
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_NIGHT:
|
||||
state = STATE_ALARM_ARMED_NIGHT
|
||||
state = AlarmControlPanelState.ARMED_NIGHT
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_VACATION:
|
||||
state = STATE_ALARM_ARMED_VACATION
|
||||
state = AlarmControlPanelState.ARMED_VACATION
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
|
||||
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
|
||||
state = AlarmControlPanelState.ARMED_CUSTOM_BYPASS
|
||||
|
||||
registry = er.async_get(hass)
|
||||
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||
|
@@ -15,13 +15,6 @@ from homeassistant.const import (
|
||||
CONF_FOR,
|
||||
CONF_PLATFORM,
|
||||
CONF_TYPE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_VACATION,
|
||||
STATE_ALARM_ARMING,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, entity_registry as er
|
||||
@@ -29,7 +22,7 @@ from homeassistant.helpers.entity import get_supported_features
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import DOMAIN
|
||||
from . import DOMAIN, AlarmControlPanelState
|
||||
from .const import AlarmControlPanelEntityFeature
|
||||
|
||||
BASIC_TRIGGER_TYPES: Final[set[str]] = {"triggered", "disarmed", "arming"}
|
||||
@@ -129,19 +122,19 @@ async def async_attach_trigger(
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Attach a trigger."""
|
||||
if config[CONF_TYPE] == "triggered":
|
||||
to_state = STATE_ALARM_TRIGGERED
|
||||
to_state = AlarmControlPanelState.TRIGGERED
|
||||
elif config[CONF_TYPE] == "disarmed":
|
||||
to_state = STATE_ALARM_DISARMED
|
||||
to_state = AlarmControlPanelState.DISARMED
|
||||
elif config[CONF_TYPE] == "arming":
|
||||
to_state = STATE_ALARM_ARMING
|
||||
to_state = AlarmControlPanelState.ARMING
|
||||
elif config[CONF_TYPE] == "armed_home":
|
||||
to_state = STATE_ALARM_ARMED_HOME
|
||||
to_state = AlarmControlPanelState.ARMED_HOME
|
||||
elif config[CONF_TYPE] == "armed_away":
|
||||
to_state = STATE_ALARM_ARMED_AWAY
|
||||
to_state = AlarmControlPanelState.ARMED_AWAY
|
||||
elif config[CONF_TYPE] == "armed_night":
|
||||
to_state = STATE_ALARM_ARMED_NIGHT
|
||||
to_state = AlarmControlPanelState.ARMED_NIGHT
|
||||
elif config[CONF_TYPE] == "armed_vacation":
|
||||
to_state = STATE_ALARM_ARMED_VACATION
|
||||
to_state = AlarmControlPanelState.ARMED_VACATION
|
||||
|
||||
state_config = {
|
||||
state_trigger.CONF_PLATFORM: "state",
|
||||
|
@@ -16,28 +16,21 @@ from homeassistant.const import (
|
||||
SERVICE_ALARM_ARM_VACATION,
|
||||
SERVICE_ALARM_DISARM,
|
||||
SERVICE_ALARM_TRIGGER,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_VACATION,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant, State
|
||||
|
||||
from . import DOMAIN
|
||||
from . import DOMAIN, AlarmControlPanelState
|
||||
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
||||
VALID_STATES: Final[set[str]] = {
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_ARMED_VACATION,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
AlarmControlPanelState.ARMED_AWAY,
|
||||
AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
|
||||
AlarmControlPanelState.ARMED_HOME,
|
||||
AlarmControlPanelState.ARMED_NIGHT,
|
||||
AlarmControlPanelState.ARMED_VACATION,
|
||||
AlarmControlPanelState.DISARMED,
|
||||
AlarmControlPanelState.TRIGGERED,
|
||||
}
|
||||
|
||||
|
||||
@@ -65,19 +58,19 @@ async def _async_reproduce_state(
|
||||
|
||||
service_data = {ATTR_ENTITY_ID: state.entity_id}
|
||||
|
||||
if state.state == STATE_ALARM_ARMED_AWAY:
|
||||
if state.state == AlarmControlPanelState.ARMED_AWAY:
|
||||
service = SERVICE_ALARM_ARM_AWAY
|
||||
elif state.state == STATE_ALARM_ARMED_CUSTOM_BYPASS:
|
||||
elif state.state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS:
|
||||
service = SERVICE_ALARM_ARM_CUSTOM_BYPASS
|
||||
elif state.state == STATE_ALARM_ARMED_HOME:
|
||||
elif state.state == AlarmControlPanelState.ARMED_HOME:
|
||||
service = SERVICE_ALARM_ARM_HOME
|
||||
elif state.state == STATE_ALARM_ARMED_NIGHT:
|
||||
elif state.state == AlarmControlPanelState.ARMED_NIGHT:
|
||||
service = SERVICE_ALARM_ARM_NIGHT
|
||||
elif state.state == STATE_ALARM_ARMED_VACATION:
|
||||
elif state.state == AlarmControlPanelState.ARMED_VACATION:
|
||||
service = SERVICE_ALARM_ARM_VACATION
|
||||
elif state.state == STATE_ALARM_DISARMED:
|
||||
elif state.state == AlarmControlPanelState.DISARMED:
|
||||
service = SERVICE_ALARM_DISARM
|
||||
elif state.state == STATE_ALARM_TRIGGERED:
|
||||
elif state.state == AlarmControlPanelState.TRIGGERED:
|
||||
service = SERVICE_ALARM_TRIGGER
|
||||
|
||||
await hass.services.async_call(
|
||||
|
@@ -7,16 +7,10 @@ import voluptuous as vol
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntity,
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
CodeFormat,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED,
|
||||
STATE_ALARM_TRIGGERED,
|
||||
)
|
||||
from homeassistant.const import ATTR_CODE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@@ -106,15 +100,15 @@ class AlarmDecoderAlarmPanel(AlarmDecoderEntity, AlarmControlPanelEntity):
|
||||
def _message_callback(self, message):
|
||||
"""Handle received messages."""
|
||||
if message.alarm_sounding or message.fire_alarm:
|
||||
self._attr_state = STATE_ALARM_TRIGGERED
|
||||
self._attr_alarm_state = AlarmControlPanelState.TRIGGERED
|
||||
elif message.armed_away:
|
||||
self._attr_state = STATE_ALARM_ARMED_AWAY
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_AWAY
|
||||
elif message.armed_home and (message.entry_delay_off or message.perimeter_only):
|
||||
self._attr_state = STATE_ALARM_ARMED_NIGHT
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_NIGHT
|
||||
elif message.armed_home:
|
||||
self._attr_state = STATE_ALARM_ARMED_HOME
|
||||
self._attr_alarm_state = AlarmControlPanelState.ARMED_HOME
|
||||
else:
|
||||
self._attr_state = STATE_ALARM_DISARMED
|
||||
self._attr_alarm_state = AlarmControlPanelState.DISARMED
|
||||
|
||||
self._attr_extra_state_attributes = {
|
||||
"ac_power": message.ac_power,
|
||||
|
@@ -157,7 +157,7 @@ class AlarmDecoderFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
class AlarmDecoderOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle AlarmDecoder options."""
|
||||
|
||||
selected_zone: str | None = None
|
||||
selected_zone: str
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize AlarmDecoder options flow."""
|
||||
|
@@ -22,7 +22,8 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Successfully connected to AlarmDecoder."
|
||||
@@ -37,7 +38,7 @@
|
||||
"title": "Configure AlarmDecoder",
|
||||
"description": "What would you like to edit?",
|
||||
"data": {
|
||||
"edit_select": "Edit"
|
||||
"edit_selection": "Edit"
|
||||
}
|
||||
},
|
||||
"arm_settings": {
|
||||
|
@@ -26,6 +26,7 @@ from homeassistant.components import (
|
||||
)
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
CodeFormat,
|
||||
)
|
||||
from homeassistant.components.climate import HVACMode
|
||||
@@ -36,10 +37,6 @@ from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
PERCENTAGE,
|
||||
STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_CUSTOM_BYPASS,
|
||||
STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_IDLE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
@@ -1317,13 +1314,13 @@ class AlexaSecurityPanelController(AlexaCapability):
|
||||
raise UnsupportedProperty(name)
|
||||
|
||||
arm_state = self.entity.state
|
||||
if arm_state == STATE_ALARM_ARMED_HOME:
|
||||
if arm_state == AlarmControlPanelState.ARMED_HOME:
|
||||
return "ARMED_STAY"
|
||||
if arm_state == STATE_ALARM_ARMED_AWAY:
|
||||
if arm_state == AlarmControlPanelState.ARMED_AWAY:
|
||||
return "ARMED_AWAY"
|
||||
if arm_state == STATE_ALARM_ARMED_NIGHT:
|
||||
if arm_state == AlarmControlPanelState.ARMED_NIGHT:
|
||||
return "ARMED_NIGHT"
|
||||
if arm_state == STATE_ALARM_ARMED_CUSTOM_BYPASS:
|
||||
if arm_state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS:
|
||||
return "ARMED_STAY"
|
||||
return "DISARMED"
|
||||
|
||||
|
@@ -9,6 +9,7 @@ from typing import Any
|
||||
|
||||
from homeassistant import core as ha
|
||||
from homeassistant.components import (
|
||||
alarm_control_panel,
|
||||
button,
|
||||
camera,
|
||||
climate,
|
||||
@@ -51,7 +52,6 @@ from homeassistant.const import (
|
||||
SERVICE_VOLUME_MUTE,
|
||||
SERVICE_VOLUME_SET,
|
||||
SERVICE_VOLUME_UP,
|
||||
STATE_ALARM_DISARMED,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.helpers import network
|
||||
@@ -1083,7 +1083,13 @@ async def async_api_arm(
|
||||
arm_state = directive.payload["armState"]
|
||||
data: dict[str, Any] = {ATTR_ENTITY_ID: entity.entity_id}
|
||||
|
||||
if entity.state != STATE_ALARM_DISARMED:
|
||||
# Per Alexa Documentation: users are not allowed to switch from armed_away
|
||||
# directly to another armed state without first disarming the system.
|
||||
# https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-securitypanelcontroller.html#arming
|
||||
if (
|
||||
entity.state == alarm_control_panel.AlarmControlPanelState.ARMED_AWAY
|
||||
and arm_state != "ARMED_AWAY"
|
||||
):
|
||||
msg = "You must disarm the system before you can set the requested arm state."
|
||||
raise AlexaSecurityPanelAuthorizationRequired(msg)
|
||||
|
||||
@@ -1133,7 +1139,7 @@ async def async_api_disarm(
|
||||
# Per Alexa Documentation: If you receive a Disarm directive, and the
|
||||
# system is already disarmed, respond with a success response,
|
||||
# not an error response.
|
||||
if entity.state == STATE_ALARM_DISARMED:
|
||||
if entity.state == alarm_control_panel.AlarmControlPanelState.DISARMED:
|
||||
return response
|
||||
|
||||
payload = directive.payload
|
||||
|
@@ -10,7 +10,7 @@
|
||||
},
|
||||
"site": {
|
||||
"data": {
|
||||
"site_nmi": "Site NMI",
|
||||
"site_id": "Site NMI",
|
||||
"site_name": "Site Name"
|
||||
},
|
||||
"description": "Select the NMI of the site you would like to add"
|
||||
|
@@ -10,12 +10,15 @@ from homeassistant.core import Event, HassJob, HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import async_call_later, async_track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .analytics import Analytics
|
||||
from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
|
||||
|
||||
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
|
||||
|
||||
DATA_COMPONENT: HassKey[Analytics] = HassKey(DOMAIN)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
|
||||
"""Set up the analytics integration."""
|
||||
@@ -52,7 +55,7 @@ async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
|
||||
websocket_api.async_register_command(hass, websocket_analytics)
|
||||
websocket_api.async_register_command(hass, websocket_analytics_preferences)
|
||||
|
||||
hass.data[DOMAIN] = analytics
|
||||
hass.data[DATA_COMPONENT] = analytics
|
||||
return True
|
||||
|
||||
|
||||
@@ -65,7 +68,7 @@ def websocket_analytics(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Return analytics preferences."""
|
||||
analytics: Analytics = hass.data[DOMAIN]
|
||||
analytics = hass.data[DATA_COMPONENT]
|
||||
connection.send_result(
|
||||
msg["id"],
|
||||
{ATTR_PREFERENCES: analytics.preferences, ATTR_ONBOARDED: analytics.onboarded},
|
||||
@@ -87,7 +90,7 @@ async def websocket_analytics_preferences(
|
||||
) -> None:
|
||||
"""Update analytics preferences."""
|
||||
preferences = msg[ATTR_PREFERENCES]
|
||||
analytics: Analytics = hass.data[DOMAIN]
|
||||
analytics = hass.data[DATA_COMPONENT]
|
||||
|
||||
await analytics.save_preferences(preferences)
|
||||
await analytics.send_analytics()
|
||||
|
@@ -29,6 +29,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.loader import (
|
||||
@@ -136,7 +137,7 @@ class Analytics:
|
||||
@property
|
||||
def supervisor(self) -> bool:
|
||||
"""Return bool if a supervisor is present."""
|
||||
return hassio.is_hassio(self.hass)
|
||||
return is_hassio(self.hass)
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Load preferences."""
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "analytics",
|
||||
"name": "Analytics",
|
||||
"after_dependencies": ["energy", "recorder"],
|
||||
"after_dependencies": ["energy", "hassio", "recorder"],
|
||||
"codeowners": ["@home-assistant/core", "@ludeeus"],
|
||||
"dependencies": ["api", "websocket_api"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/analytics",
|
||||
|
@@ -16,7 +16,6 @@ from homeassistant.config_entries import (
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
OptionsFlowWithConfigEntry,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -27,6 +26,7 @@ from homeassistant.helpers.selector import (
|
||||
)
|
||||
|
||||
from .const import (
|
||||
CONF_TRACKED_ADDONS,
|
||||
CONF_TRACKED_CUSTOM_INTEGRATIONS,
|
||||
CONF_TRACKED_INTEGRATIONS,
|
||||
DOMAIN,
|
||||
@@ -45,9 +45,11 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> HomeassistantAnalyticsOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return HomeassistantAnalyticsOptionsFlowHandler(config_entry)
|
||||
return HomeassistantAnalyticsOptionsFlowHandler()
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -55,8 +57,12 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
|
||||
CONF_TRACKED_CUSTOM_INTEGRATIONS
|
||||
if all(
|
||||
[
|
||||
not user_input.get(CONF_TRACKED_ADDONS),
|
||||
not user_input.get(CONF_TRACKED_INTEGRATIONS),
|
||||
not user_input.get(CONF_TRACKED_CUSTOM_INTEGRATIONS),
|
||||
]
|
||||
):
|
||||
errors["base"] = "no_integrations_selected"
|
||||
else:
|
||||
@@ -64,6 +70,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
title="Home Assistant Analytics Insights",
|
||||
data={},
|
||||
options={
|
||||
CONF_TRACKED_ADDONS: user_input.get(CONF_TRACKED_ADDONS, []),
|
||||
CONF_TRACKED_INTEGRATIONS: user_input.get(
|
||||
CONF_TRACKED_INTEGRATIONS, []
|
||||
),
|
||||
@@ -77,6 +84,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
session=async_get_clientsession(self.hass)
|
||||
)
|
||||
try:
|
||||
addons = await client.get_addons()
|
||||
integrations = await client.get_integrations()
|
||||
custom_integrations = await client.get_custom_integrations()
|
||||
except HomeassistantAnalyticsConnectionError:
|
||||
@@ -99,6 +107,13 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_TRACKED_ADDONS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=list(addons),
|
||||
multiple=True,
|
||||
sort=True,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options,
|
||||
@@ -118,7 +133,7 @@ class HomeassistantAnalyticsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
|
||||
|
||||
class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle Homeassistant Analytics options."""
|
||||
|
||||
async def async_step_init(
|
||||
@@ -127,14 +142,19 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
"""Manage the options."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
if not user_input.get(CONF_TRACKED_INTEGRATIONS) and not user_input.get(
|
||||
CONF_TRACKED_CUSTOM_INTEGRATIONS
|
||||
if all(
|
||||
[
|
||||
not user_input.get(CONF_TRACKED_ADDONS),
|
||||
not user_input.get(CONF_TRACKED_INTEGRATIONS),
|
||||
not user_input.get(CONF_TRACKED_CUSTOM_INTEGRATIONS),
|
||||
]
|
||||
):
|
||||
errors["base"] = "no_integrations_selected"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title="",
|
||||
data={
|
||||
CONF_TRACKED_ADDONS: user_input.get(CONF_TRACKED_ADDONS, []),
|
||||
CONF_TRACKED_INTEGRATIONS: user_input.get(
|
||||
CONF_TRACKED_INTEGRATIONS, []
|
||||
),
|
||||
@@ -148,6 +168,7 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
session=async_get_clientsession(self.hass)
|
||||
)
|
||||
try:
|
||||
addons = await client.get_addons()
|
||||
integrations = await client.get_integrations()
|
||||
custom_integrations = await client.get_custom_integrations()
|
||||
except HomeassistantAnalyticsConnectionError:
|
||||
@@ -168,6 +189,13 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_TRACKED_ADDONS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=list(addons),
|
||||
multiple=True,
|
||||
sort=True,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_TRACKED_INTEGRATIONS): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=options,
|
||||
@@ -184,6 +212,6 @@ class HomeassistantAnalyticsOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
),
|
||||
},
|
||||
),
|
||||
self.options,
|
||||
self.config_entry.options,
|
||||
),
|
||||
)
|
||||
|
@@ -4,6 +4,7 @@ import logging
|
||||
|
||||
DOMAIN = "analytics_insights"
|
||||
|
||||
CONF_TRACKED_ADDONS = "tracked_addons"
|
||||
CONF_TRACKED_INTEGRATIONS = "tracked_integrations"
|
||||
CONF_TRACKED_CUSTOM_INTEGRATIONS = "tracked_custom_integrations"
|
||||
|
||||
|
@@ -12,11 +12,13 @@ from python_homeassistant_analytics import (
|
||||
HomeassistantAnalyticsConnectionError,
|
||||
HomeassistantAnalyticsNotModifiedError,
|
||||
)
|
||||
from python_homeassistant_analytics.models import Addon
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
CONF_TRACKED_ADDONS,
|
||||
CONF_TRACKED_CUSTOM_INTEGRATIONS,
|
||||
CONF_TRACKED_INTEGRATIONS,
|
||||
DOMAIN,
|
||||
@@ -31,6 +33,9 @@ if TYPE_CHECKING:
|
||||
class AnalyticsData:
|
||||
"""Analytics data class."""
|
||||
|
||||
active_installations: int
|
||||
reports_integrations: int
|
||||
addons: dict[str, int]
|
||||
core_integrations: dict[str, int]
|
||||
custom_integrations: dict[str, int]
|
||||
|
||||
@@ -51,6 +56,7 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
|
||||
update_interval=timedelta(hours=12),
|
||||
)
|
||||
self._client = client
|
||||
self._tracked_addons = self.config_entry.options.get(CONF_TRACKED_ADDONS, [])
|
||||
self._tracked_integrations = self.config_entry.options[
|
||||
CONF_TRACKED_INTEGRATIONS
|
||||
]
|
||||
@@ -60,6 +66,7 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
|
||||
|
||||
async def _async_update_data(self) -> AnalyticsData:
|
||||
try:
|
||||
addons_data = await self._client.get_addons()
|
||||
data = await self._client.get_current_analytics()
|
||||
custom_data = await self._client.get_custom_integrations()
|
||||
except HomeassistantAnalyticsConnectionError as err:
|
||||
@@ -68,6 +75,9 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
|
||||
) from err
|
||||
except HomeassistantAnalyticsNotModifiedError:
|
||||
return self.data
|
||||
addons = {
|
||||
addon: get_addon_value(addons_data, addon) for addon in self._tracked_addons
|
||||
}
|
||||
core_integrations = {
|
||||
integration: data.integrations.get(integration, 0)
|
||||
for integration in self._tracked_integrations
|
||||
@@ -76,7 +86,20 @@ class HomeassistantAnalyticsDataUpdateCoordinator(DataUpdateCoordinator[Analytic
|
||||
integration: get_custom_integration_value(custom_data, integration)
|
||||
for integration in self._tracked_custom_integrations
|
||||
}
|
||||
return AnalyticsData(core_integrations, custom_integrations)
|
||||
return AnalyticsData(
|
||||
data.active_installations,
|
||||
data.reports_integrations,
|
||||
addons,
|
||||
core_integrations,
|
||||
custom_integrations,
|
||||
)
|
||||
|
||||
|
||||
def get_addon_value(data: dict[str, Addon], name_slug: str) -> int:
|
||||
"""Get addon value."""
|
||||
if name_slug in data:
|
||||
return data[name_slug].total
|
||||
return 0
|
||||
|
||||
|
||||
def get_custom_integration_value(
|
||||
|
@@ -6,6 +6,12 @@
|
||||
},
|
||||
"custom_integrations": {
|
||||
"default": "mdi:puzzle-edit"
|
||||
},
|
||||
"total_active_installations": {
|
||||
"default": "mdi:puzzle"
|
||||
},
|
||||
"total_reports_integrations": {
|
||||
"default": "mdi:puzzle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,6 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["python_homeassistant_analytics"],
|
||||
"requirements": ["python-homeassistant-analytics==0.7.0"],
|
||||
"requirements": ["python-homeassistant-analytics==0.8.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
@@ -29,6 +29,20 @@ class AnalyticsSensorEntityDescription(SensorEntityDescription):
|
||||
value_fn: Callable[[AnalyticsData], StateType]
|
||||
|
||||
|
||||
def get_addon_entity_description(
|
||||
name_slug: str,
|
||||
) -> AnalyticsSensorEntityDescription:
|
||||
"""Get addon entity description."""
|
||||
return AnalyticsSensorEntityDescription(
|
||||
key=f"addon_{name_slug}_active_installations",
|
||||
translation_key="addons",
|
||||
name=name_slug,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.addons.get(name_slug),
|
||||
)
|
||||
|
||||
|
||||
def get_core_integration_entity_description(
|
||||
domain: str, name: str
|
||||
) -> AnalyticsSensorEntityDescription:
|
||||
@@ -57,6 +71,26 @@ def get_custom_integration_entity_description(
|
||||
)
|
||||
|
||||
|
||||
GENERAL_SENSORS = [
|
||||
AnalyticsSensorEntityDescription(
|
||||
key="total_active_installations",
|
||||
translation_key="total_active_installations",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.active_installations,
|
||||
),
|
||||
AnalyticsSensorEntityDescription(
|
||||
key="total_reports_integrations",
|
||||
translation_key="total_reports_integrations",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.reports_integrations,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AnalyticsInsightsConfigEntry,
|
||||
@@ -69,6 +103,13 @@ async def async_setup_entry(
|
||||
analytics_data.coordinator
|
||||
)
|
||||
entities: list[HomeassistantAnalyticsSensor] = []
|
||||
entities.extend(
|
||||
HomeassistantAnalyticsSensor(
|
||||
coordinator,
|
||||
get_addon_entity_description(addon_name_slug),
|
||||
)
|
||||
for addon_name_slug in coordinator.data.addons
|
||||
)
|
||||
entities.extend(
|
||||
HomeassistantAnalyticsSensor(
|
||||
coordinator,
|
||||
@@ -85,6 +126,12 @@ async def async_setup_entry(
|
||||
)
|
||||
for integration_domain in coordinator.data.custom_integrations
|
||||
)
|
||||
|
||||
entities.extend(
|
||||
HomeassistantAnalyticsSensor(coordinator, entity_description)
|
||||
for entity_description in GENERAL_SENSORS
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
@@ -3,10 +3,12 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"tracked_addons": "Addons",
|
||||
"tracked_integrations": "Integrations",
|
||||
"tracked_custom_integrations": "Custom integrations"
|
||||
},
|
||||
"data_description": {
|
||||
"tracked_addons": "Select the addons you want to track",
|
||||
"tracked_integrations": "Select the integrations you want to track",
|
||||
"tracked_custom_integrations": "Select the custom integrations you want to track"
|
||||
}
|
||||
@@ -17,17 +19,19 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"error": {
|
||||
"no_integration_selected": "You must select at least one integration to track"
|
||||
"no_integrations_selected": "You must select at least one integration to track"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"tracked_addons": "[%key:component::analytics_insights::config::step::user::data::tracked_addons%]",
|
||||
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_integrations%]",
|
||||
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data::tracked_custom_integrations%]"
|
||||
},
|
||||
"data_description": {
|
||||
"tracked_addons": "[%key:component::analytics_insights::config::step::user::data_description::tracked_addons%]",
|
||||
"tracked_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_integrations%]",
|
||||
"tracked_custom_integrations": "[%key:component::analytics_insights::config::step::user::data_description::tracked_custom_integrations%]"
|
||||
}
|
||||
@@ -37,13 +41,19 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"error": {
|
||||
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]"
|
||||
"no_integrations_selected": "[%key:component::analytics_insights::config::error::no_integrations_selected%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"custom_integrations": {
|
||||
"name": "{custom_integration_domain} (custom)"
|
||||
},
|
||||
"total_active_installations": {
|
||||
"name": "Total active installations"
|
||||
},
|
||||
"total_reports_integrations": {
|
||||
"name": "Total reported integrations"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from pydroid_ipcam import PyDroidIPCam
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -15,8 +14,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
@@ -26,7 +24,9 @@ PLATFORMS: list[Platform] = [
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: AndroidIPCamConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Android IP Webcam from a config entry."""
|
||||
websession = async_get_clientsession(hass)
|
||||
cam = PyDroidIPCam(
|
||||
@@ -40,16 +40,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = AndroidIPCamDataUpdateCoordinator(hass, entry, cam)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: AndroidIPCamConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@@ -7,12 +7,11 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, MOTION_ACTIVE
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
from .const import MOTION_ACTIVE
|
||||
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
|
||||
from .entity import AndroidIPCamBaseEntity
|
||||
|
||||
BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
|
||||
@@ -24,16 +23,12 @@ BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidIPCamConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the IP Webcam sensors from config entry."""
|
||||
|
||||
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
|
||||
async_add_entities([IPWebcamBinarySensor(coordinator)])
|
||||
async_add_entities([IPWebcamBinarySensor(config_entry.runtime_data)])
|
||||
|
||||
|
||||
class IPWebcamBinarySensor(AndroidIPCamBaseEntity, BinarySensorEntity):
|
||||
|
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -15,21 +14,17 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidIPCamConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the IP Webcam camera from config entry."""
|
||||
filter_urllib3_logging()
|
||||
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
|
||||
async_add_entities([IPWebcamCamera(coordinator)])
|
||||
async_add_entities([IPWebcamCamera(config_entry.runtime_data)])
|
||||
|
||||
|
||||
class IPWebcamCamera(MjpegCamera):
|
||||
|
@@ -15,19 +15,22 @@ from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type AndroidIPCamConfigEntry = ConfigEntry[AndroidIPCamDataUpdateCoordinator]
|
||||
|
||||
|
||||
class AndroidIPCamDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Coordinator class for the Android IP Webcam."""
|
||||
|
||||
config_entry: AndroidIPCamConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidIPCamConfigEntry,
|
||||
cam: PyDroidIPCam,
|
||||
) -> None:
|
||||
"""Initialize the Android IP Webcam."""
|
||||
self.hass = hass
|
||||
self.config_entry: ConfigEntry = config_entry
|
||||
self.cam = cam
|
||||
super().__init__(
|
||||
self.hass,
|
||||
|
@@ -13,14 +13,12 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
|
||||
from .entity import AndroidIPCamBaseEntity
|
||||
|
||||
|
||||
@@ -120,19 +118,21 @@ SENSOR_TYPES: tuple[AndroidIPWebcamSensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidIPCamConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the IP Webcam sensors from config entry."""
|
||||
|
||||
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
coordinator = config_entry.runtime_data
|
||||
sensor_types = [
|
||||
sensor
|
||||
for sensor in SENSOR_TYPES
|
||||
if sensor.key
|
||||
in [*coordinator.cam.enabled_sensors, "audio_connections", "video_connections"]
|
||||
in [
|
||||
*coordinator.cam.enabled_sensors,
|
||||
"audio_connections",
|
||||
"video_connections",
|
||||
]
|
||||
]
|
||||
async_add_entities(
|
||||
IPWebcamSensor(coordinator, description) for description in sensor_types
|
||||
|
@@ -9,13 +9,11 @@ from typing import Any
|
||||
from pydroid_ipcam import PyDroidIPCam
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AndroidIPCamDataUpdateCoordinator
|
||||
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
|
||||
from .entity import AndroidIPCamBaseEntity
|
||||
|
||||
|
||||
@@ -113,14 +111,12 @@ SWITCH_TYPES: tuple[AndroidIPWebcamSwitchEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidIPCamConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the IP Webcam switches from config entry."""
|
||||
|
||||
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
coordinator = config_entry.runtime_data
|
||||
switch_types = [
|
||||
switch
|
||||
for switch in SWITCH_TYPES
|
||||
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
@@ -40,6 +41,7 @@ from .const import (
|
||||
CONF_ADB_SERVER_IP,
|
||||
CONF_ADB_SERVER_PORT,
|
||||
CONF_ADBKEY,
|
||||
CONF_SCREENCAP_INTERVAL,
|
||||
CONF_STATE_DETECTION_RULES,
|
||||
DEFAULT_ADB_SERVER_PORT,
|
||||
DEVICE_ANDROIDTV,
|
||||
@@ -66,6 +68,8 @@ RELOAD_OPTIONS = [CONF_STATE_DETECTION_RULES]
|
||||
|
||||
_INVALID_MACS = {"ff:ff:ff:ff:ff:ff"}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AndroidTVRuntimeData:
|
||||
@@ -157,6 +161,32 @@ async def async_connect_androidtv(
|
||||
return aftv, None
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Migrate old entry."""
|
||||
_LOGGER.debug(
|
||||
"Migrating configuration from version %s.%s", entry.version, entry.minor_version
|
||||
)
|
||||
|
||||
if entry.version == 1:
|
||||
new_options = {**entry.options}
|
||||
|
||||
# Migrate MinorVersion 1 -> MinorVersion 2: New option
|
||||
if entry.minor_version < 2:
|
||||
new_options = {**new_options, CONF_SCREENCAP_INTERVAL: 0}
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, options=new_options, minor_version=2, version=1
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migration to configuration version %s.%s successful",
|
||||
entry.version,
|
||||
entry.minor_version,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AndroidTVConfigEntry) -> bool:
|
||||
"""Set up Android Debug Bridge platform."""
|
||||
|
||||
|
@@ -13,7 +13,7 @@ from homeassistant.config_entries import (
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlowWithConfigEntry,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT
|
||||
from homeassistant.core import callback
|
||||
@@ -34,7 +34,7 @@ from .const import (
|
||||
CONF_APPS,
|
||||
CONF_EXCLUDE_UNNAMED_APPS,
|
||||
CONF_GET_SOURCES,
|
||||
CONF_SCREENCAP,
|
||||
CONF_SCREENCAP_INTERVAL,
|
||||
CONF_STATE_DETECTION_RULES,
|
||||
CONF_TURN_OFF_COMMAND,
|
||||
CONF_TURN_ON_COMMAND,
|
||||
@@ -43,7 +43,7 @@ from .const import (
|
||||
DEFAULT_EXCLUDE_UNNAMED_APPS,
|
||||
DEFAULT_GET_SOURCES,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_SCREENCAP,
|
||||
DEFAULT_SCREENCAP_INTERVAL,
|
||||
DEVICE_CLASSES,
|
||||
DOMAIN,
|
||||
PROP_ETHMAC,
|
||||
@@ -76,6 +76,7 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow."""
|
||||
|
||||
VERSION = 1
|
||||
MINOR_VERSION = 2
|
||||
|
||||
@callback
|
||||
def _show_setup_form(
|
||||
@@ -185,16 +186,14 @@ class AndroidTVFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
class OptionsFlowHandler(OptionsFlow):
|
||||
"""Handle an option flow for Android Debug Bridge."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
super().__init__(config_entry)
|
||||
|
||||
self._apps: dict[str, Any] = self.options.setdefault(CONF_APPS, {})
|
||||
self._state_det_rules: dict[str, Any] = self.options.setdefault(
|
||||
CONF_STATE_DETECTION_RULES, {}
|
||||
self._apps: dict[str, Any] = dict(config_entry.options.get(CONF_APPS, {}))
|
||||
self._state_det_rules: dict[str, Any] = dict(
|
||||
config_entry.options.get(CONF_STATE_DETECTION_RULES, {})
|
||||
)
|
||||
self._conf_app_id: str | None = None
|
||||
self._conf_rule_id: str | None = None
|
||||
@@ -236,7 +235,7 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
SelectOptionDict(value=k, label=v) for k, v in apps_list.items()
|
||||
]
|
||||
rules = [RULES_NEW_ID, *self._state_det_rules]
|
||||
options = self.options
|
||||
options = self.config_entry.options
|
||||
|
||||
data_schema = vol.Schema(
|
||||
{
|
||||
@@ -253,10 +252,12 @@ class OptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
CONF_EXCLUDE_UNNAMED_APPS, DEFAULT_EXCLUDE_UNNAMED_APPS
|
||||
),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_SCREENCAP,
|
||||
default=options.get(CONF_SCREENCAP, DEFAULT_SCREENCAP),
|
||||
): bool,
|
||||
vol.Required(
|
||||
CONF_SCREENCAP_INTERVAL,
|
||||
default=options.get(
|
||||
CONF_SCREENCAP_INTERVAL, DEFAULT_SCREENCAP_INTERVAL
|
||||
),
|
||||
): vol.All(vol.Coerce(int), vol.Clamp(min=0, max=15)),
|
||||
vol.Optional(
|
||||
CONF_TURN_OFF_COMMAND,
|
||||
description={
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user