mirror of
https://github.com/home-assistant/core.git
synced 2025-11-25 18:48:05 +00:00
Compare commits
1411 Commits
epenet-pat
...
adguard/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
653a126791 | ||
|
|
05917a9fcd | ||
|
|
bd0ab4d1fe | ||
|
|
80151b205d | ||
|
|
4488fdd2d6 | ||
|
|
a6e0bea805 | ||
|
|
994619e179 | ||
|
|
4db5be73a7 | ||
|
|
3cfedd1721 | ||
|
|
2f1301abaf | ||
|
|
21d61ef401 | ||
|
|
6850f9622a | ||
|
|
2b2bb79505 | ||
|
|
d97998e2e1 | ||
|
|
3ef62c97ca | ||
|
|
5cca95ab2f | ||
|
|
a4f0a21c8e | ||
|
|
11a2b5df6a | ||
|
|
07e2c8a610 | ||
|
|
43783ed896 | ||
|
|
a206604df5 | ||
|
|
2e82ac81b2 | ||
|
|
5139e9e566 | ||
|
|
c53674531c | ||
|
|
a04244ad25 | ||
|
|
b27b357b91 | ||
|
|
01e38853c0 | ||
|
|
06158fc9a1 | ||
|
|
e5968084a2 | ||
|
|
263839a6c0 | ||
|
|
931b2c2db0 | ||
|
|
8e26112db1 | ||
|
|
b1286af423 | ||
|
|
bd02e279cf | ||
|
|
6e5be843d6 | ||
|
|
5b1d86a04b | ||
|
|
1514013c3b | ||
|
|
54ed290cc1 | ||
|
|
1106f4f0e2 | ||
|
|
f73e92a34a | ||
|
|
74ad5066e2 | ||
|
|
4202a665af | ||
|
|
c9ddbe39ce | ||
|
|
8a2e8d2c61 | ||
|
|
ca2e8bfb56 | ||
|
|
c0772f3957 | ||
|
|
0b96da3b24 | ||
|
|
4c07b2b290 | ||
|
|
f699d95ea0 | ||
|
|
f6b9a0eb29 | ||
|
|
71c665ed49 | ||
|
|
85a1afb174 | ||
|
|
9668a68c28 | ||
|
|
a06aa8edfe | ||
|
|
4e30a5d930 | ||
|
|
696550a7f2 | ||
|
|
c064d23a99 | ||
|
|
ac7b063c2c | ||
|
|
e0778c8e2e | ||
|
|
2ba5a96d5b | ||
|
|
13c9fb6e37 | ||
|
|
102bb1f694 | ||
|
|
e5b2d44e8e | ||
|
|
4d4ad900b1 | ||
|
|
acc136af19 | ||
|
|
0f12a40eb2 | ||
|
|
bf124daf72 | ||
|
|
1682ced5cc | ||
|
|
80b316bc70 | ||
|
|
00d2340d4b | ||
|
|
514a329580 | ||
|
|
f2b8bb01bf | ||
|
|
30153ab059 | ||
|
|
2957b15ede | ||
|
|
12ace95f3e | ||
|
|
babe19767d | ||
|
|
d01843e1ab | ||
|
|
9964cb512a | ||
|
|
ae38214b7c | ||
|
|
9812286801 | ||
|
|
32a40e5919 | ||
|
|
97de944a14 | ||
|
|
c9bd87f4b3 | ||
|
|
ac46568996 | ||
|
|
7c1b8ee02c | ||
|
|
aa6901265d | ||
|
|
b76e9ad1c0 | ||
|
|
edb8007c65 | ||
|
|
956a29411f | ||
|
|
1a2361050b | ||
|
|
0c9e92f6f9 | ||
|
|
bfdff46859 | ||
|
|
9a22808499 | ||
|
|
88b373af41 | ||
|
|
dea2f37e8f | ||
|
|
30cce68e0b | ||
|
|
985eff972a | ||
|
|
31ca332158 | ||
|
|
bf76c1601d | ||
|
|
e572f8d48f | ||
|
|
482b5d49a3 | ||
|
|
126fd217e7 | ||
|
|
0327b0e1ec | ||
|
|
3d5a7b4813 | ||
|
|
e0bb30f63b | ||
|
|
e5ae58c5df | ||
|
|
13e4bb4b93 | ||
|
|
d5fd27d2a2 | ||
|
|
0a034b9984 | ||
|
|
6a8106c0eb | ||
|
|
2cacfc7413 | ||
|
|
388ab5c16c | ||
|
|
81ea6f8c25 | ||
|
|
4f885994b7 | ||
|
|
25e2c9ee80 | ||
|
|
12c04f5571 | ||
|
|
3ad1c6a47a | ||
|
|
e7e13ecc74 | ||
|
|
991b8d2040 | ||
|
|
43fadbf6b4 | ||
|
|
ca79d37135 | ||
|
|
df8ef15535 | ||
|
|
249c1530d0 | ||
|
|
081b769abc | ||
|
|
b8b101d747 | ||
|
|
a19be192e0 | ||
|
|
92da82a200 | ||
|
|
820ba1dfba | ||
|
|
63c8962f09 | ||
|
|
c1a6996549 | ||
|
|
05253841af | ||
|
|
f2ef0503a0 | ||
|
|
938da38fc3 | ||
|
|
9311a87bf5 | ||
|
|
b45294ded3 | ||
|
|
82d3190016 | ||
|
|
d8cbcc1977 | ||
|
|
4b69543515 | ||
|
|
97ef4a35b9 | ||
|
|
f782c78650 | ||
|
|
139ed34c74 | ||
|
|
7f14d013ac | ||
|
|
963e27dda4 | ||
|
|
b8e3d57fea | ||
|
|
0de2a16d0f | ||
|
|
c8c2413a09 | ||
|
|
291331f878 | ||
|
|
a13cdbdf3d | ||
|
|
1bf713f279 | ||
|
|
10c8ee417b | ||
|
|
b23134f4f1 | ||
|
|
f45a6f806b | ||
|
|
d3857a00d5 | ||
|
|
8c9b90a9f9 | ||
|
|
4eedc88935 | ||
|
|
343ea1b82d | ||
|
|
36e13653d2 | ||
|
|
80444b2165 | ||
|
|
262f06dd2b | ||
|
|
bd87119c2e | ||
|
|
0dfa037aa8 | ||
|
|
c32a471573 | ||
|
|
97b7e51171 | ||
|
|
433712b407 | ||
|
|
5d87e0f429 | ||
|
|
acb087f1e5 | ||
|
|
10c12623bf | ||
|
|
2fe20553b3 | ||
|
|
b431bb197a | ||
|
|
eb9d625926 | ||
|
|
3a69534b09 | ||
|
|
8f2cedcb73 | ||
|
|
3658953ff3 | ||
|
|
0be5893e37 | ||
|
|
c87e38c4cf | ||
|
|
4874610ad6 | ||
|
|
9180282fc6 | ||
|
|
118f30f32e | ||
|
|
bd10da126f | ||
|
|
b73a7928ca | ||
|
|
3e20c2ea93 | ||
|
|
60130d3d68 | ||
|
|
c45ede2e5d | ||
|
|
e167061f53 | ||
|
|
5560fb6c9e | ||
|
|
9808b6c961 | ||
|
|
e8cfde579e | ||
|
|
f695fb4d51 | ||
|
|
a0e0549d90 | ||
|
|
ba034c6c8c | ||
|
|
008bb85c59 | ||
|
|
cf1c1294d3 | ||
|
|
11d5d314cc | ||
|
|
6f0de3071a | ||
|
|
87d2597292 | ||
|
|
437bc04fe8 | ||
|
|
67a0d6a187 | ||
|
|
abb52bca81 | ||
|
|
d2d6889278 | ||
|
|
bdca592219 | ||
|
|
5c0c7b9ec3 | ||
|
|
9717599fb9 | ||
|
|
4d7de2f814 | ||
|
|
779590ce1c | ||
|
|
f3a185ff9c | ||
|
|
5a5a106984 | ||
|
|
796b421d99 | ||
|
|
0c03e8dbe9 | ||
|
|
47cf4e3ffe | ||
|
|
0ea0fc151d | ||
|
|
b7e5afec9f | ||
|
|
7a2bb67e82 | ||
|
|
e0612bec07 | ||
|
|
a06f4b6776 | ||
|
|
275670a526 | ||
|
|
d0d62526dd | ||
|
|
aefdf412b0 | ||
|
|
56ab6b2512 | ||
|
|
d1dea85cf5 | ||
|
|
84b0d39763 | ||
|
|
3aff225bc3 | ||
|
|
04458e01be | ||
|
|
ae51cfb8c0 | ||
|
|
c116a9c037 | ||
|
|
fb58758684 | ||
|
|
25fbcbc68c | ||
|
|
a670286b45 | ||
|
|
52ba55b17f | ||
|
|
ff0fc98c36 | ||
|
|
9f78a2263d | ||
|
|
9b4696a80b | ||
|
|
70fe8cae39 | ||
|
|
95eb45ab08 | ||
|
|
84f8e57141 | ||
|
|
f484b6df0d | ||
|
|
34c1d45ee0 | ||
|
|
09a105d9ad | ||
|
|
6bd1787d0a | ||
|
|
37040f5064 | ||
|
|
531397ec07 | ||
|
|
d6cc0f81de | ||
|
|
f8ef8a466a | ||
|
|
713015e26a | ||
|
|
f9c1e81c5e | ||
|
|
0549d113e6 | ||
|
|
0d842978ec | ||
|
|
55476ef6ea | ||
|
|
0e130d8fdd | ||
|
|
20bcb84956 | ||
|
|
bbb1d57081 | ||
|
|
121406569b | ||
|
|
4866c775ce | ||
|
|
7c5ab12270 | ||
|
|
099edfac20 | ||
|
|
aa31df0fd5 | ||
|
|
13fbeb6cdb | ||
|
|
8d557447df | ||
|
|
e6e3f2455f | ||
|
|
c9c518ee84 | ||
|
|
214731e964 | ||
|
|
c4b09c9a0a | ||
|
|
f5b5b2fb70 | ||
|
|
bb3cdd382b | ||
|
|
8d09b5c273 | ||
|
|
d92fa7fa72 | ||
|
|
0c45b7f615 | ||
|
|
bfa1116115 | ||
|
|
4984237987 | ||
|
|
3839573151 | ||
|
|
e02dc53df3 | ||
|
|
bedae1e12c | ||
|
|
b4eb73be98 | ||
|
|
0ac3f776fa | ||
|
|
8e8a4fff11 | ||
|
|
579ffcc64d | ||
|
|
81943fb31d | ||
|
|
70dd0bf12e | ||
|
|
c2d462c1e7 | ||
|
|
49e050cc60 | ||
|
|
f6d829a2f3 | ||
|
|
e44e3b6f25 | ||
|
|
af603661c0 | ||
|
|
35c6113777 | ||
|
|
3c2f729ddc | ||
|
|
0d63cb765f | ||
|
|
3cb414511b | ||
|
|
f55c36d42d | ||
|
|
26bb301cc0 | ||
|
|
4159e483ee | ||
|
|
7eb6f7cc07 | ||
|
|
a7d01b0b03 | ||
|
|
1e5cfddf83 | ||
|
|
006fc5b10a | ||
|
|
35a4b685b3 | ||
|
|
b166818ef4 | ||
|
|
34cd9f11d0 | ||
|
|
0711d62085 | ||
|
|
f70aeafb5f | ||
|
|
e2279b3589 | ||
|
|
87b68e99ec | ||
|
|
b6c8b787e8 | ||
|
|
78f26edc29 | ||
|
|
5e6a72de90 | ||
|
|
dcc559f8b6 | ||
|
|
eda49cced0 | ||
|
|
14e41ab119 | ||
|
|
46151456d8 | ||
|
|
39773a022a | ||
|
|
5f49a6450f | ||
|
|
dc8425c580 | ||
|
|
910bd371e4 | ||
|
|
802a225e11 | ||
|
|
84f66fa689 | ||
|
|
0b7e88d0e0 | ||
|
|
1fcaf95df5 | ||
|
|
6c7434531f | ||
|
|
5ec1c2b68b | ||
|
|
d8636d8346 | ||
|
|
434763c74d | ||
|
|
8cd2c1b43b | ||
|
|
44711787a4 | ||
|
|
98fd0ee683 | ||
|
|
303e4ce961 | ||
|
|
76f29298cd | ||
|
|
17f5d0a69f | ||
|
|
90561de438 | ||
|
|
aedd48c298 | ||
|
|
febbb85532 | ||
|
|
af67a35b75 | ||
|
|
dd34d458f5 | ||
|
|
603d4bcf87 | ||
|
|
2dadc1f2b3 | ||
|
|
936151fae5 | ||
|
|
9760eb7f2b | ||
|
|
7851bed00c | ||
|
|
6aba0b20c6 | ||
|
|
cadfed2348 | ||
|
|
44e2fa6996 | ||
|
|
d0ff617e17 | ||
|
|
8e499569a4 | ||
|
|
5e0ebddd6f | ||
|
|
c0f61f6c2b | ||
|
|
df60de38b0 | ||
|
|
cb086bb8e9 | ||
|
|
ee2e9dc7d6 | ||
|
|
85cd3c68b7 | ||
|
|
1b0b6e63f2 | ||
|
|
12fc79e8d3 | ||
|
|
ca2e7b9509 | ||
|
|
8e8becc43e | ||
|
|
dcec6c3dc8 | ||
|
|
c0e59c4508 | ||
|
|
cd379aadbf | ||
|
|
ccdd54b187 | ||
|
|
3f22dbaa2e | ||
|
|
c18dc0a9ab | ||
|
|
f0e4296d93 | ||
|
|
b3750109c6 | ||
|
|
93025c9845 | ||
|
|
df348644b1 | ||
|
|
8749b0d750 | ||
|
|
a6a1519c06 | ||
|
|
3068e19843 | ||
|
|
55feb1e735 | ||
|
|
bb7dc69131 | ||
|
|
aa9003a524 | ||
|
|
4e9da5249d | ||
|
|
f502739df2 | ||
|
|
0f2ff29378 | ||
|
|
2921e7ed3c | ||
|
|
25d44e8d37 | ||
|
|
0a480a26a3 | ||
|
|
d5da64dd8d | ||
|
|
92adcd8635 | ||
|
|
ee0c4b15c2 | ||
|
|
507f54198e | ||
|
|
0ed342b433 | ||
|
|
363c86faf3 | ||
|
|
095a7ad060 | ||
|
|
ab5981bbbd | ||
|
|
ac2fb53dfd | ||
|
|
02ff5de1ff | ||
|
|
5cd5d480d9 | ||
|
|
a3c7d772fc | ||
|
|
fe0c69dba7 | ||
|
|
e5365234c3 | ||
|
|
1531175bd3 | ||
|
|
62add59ff4 | ||
|
|
d8daca657b | ||
|
|
1891da46ea | ||
|
|
22ae894745 | ||
|
|
160810c69d | ||
|
|
2ae23b920a | ||
|
|
a7edfb082f | ||
|
|
3ac203b05f | ||
|
|
7c3eb19fc4 | ||
|
|
70c6fac743 | ||
|
|
e19d7250d5 | ||
|
|
a850d5dba7 | ||
|
|
0cf0f10654 | ||
|
|
8429f154ca | ||
|
|
7b4f5ad362 | ||
|
|
583b439557 | ||
|
|
05922de102 | ||
|
|
7675a44b90 | ||
|
|
1e4d645683 | ||
|
|
b5ae04605a | ||
|
|
2240d6b94c | ||
|
|
d1536ee636 | ||
|
|
8a926add7a | ||
|
|
31f769900a | ||
|
|
33ad777664 | ||
|
|
59a4e4a337 | ||
|
|
66a39933b0 | ||
|
|
ad395e3bba | ||
|
|
cfc6f2c229 | ||
|
|
63aa41c766 | ||
|
|
037e0e93d3 | ||
|
|
db8b5865b3 | ||
|
|
bd2ccc6672 | ||
|
|
bb63d40cdf | ||
|
|
65285b8885 | ||
|
|
326b8f2b4f | ||
|
|
9f3df52fcc | ||
|
|
875838c277 | ||
|
|
adaafd1fda | ||
|
|
50c5efddaa | ||
|
|
c4be054161 | ||
|
|
61186356f3 | ||
|
|
9d60a19440 | ||
|
|
108c212855 | ||
|
|
ae8db81c4e | ||
|
|
51c970d1d0 | ||
|
|
d2d47cb607 | ||
|
|
b7a5447c8b | ||
|
|
2f80780f75 | ||
|
|
053ec2598f | ||
|
|
85bed4ca77 | ||
|
|
d0d268ffdc | ||
|
|
a709fa5f6c | ||
|
|
0c99638129 | ||
|
|
8a03ab2f64 | ||
|
|
d2ad5b43f2 | ||
|
|
4fae49158c | ||
|
|
36268ffb73 | ||
|
|
1bd70454e1 | ||
|
|
dbc53b99c1 | ||
|
|
9ec3aee8aa | ||
|
|
8d50754056 | ||
|
|
6ee71dae35 | ||
|
|
9605921857 | ||
|
|
6f06eb5ecc | ||
|
|
4ca620e450 | ||
|
|
69c5668b13 | ||
|
|
17fc1c5dbc | ||
|
|
6cfe6ed543 | ||
|
|
b9fb4469d8 | ||
|
|
f53c581845 | ||
|
|
a39710f9bc | ||
|
|
47734f54e8 | ||
|
|
52b3636e52 | ||
|
|
7aced2522a | ||
|
|
5b2d43dffb | ||
|
|
b4d6a44c21 | ||
|
|
adf8644cc3 | ||
|
|
acb6dc9a4f | ||
|
|
463796fb4a | ||
|
|
c74a298b5b | ||
|
|
fb30535730 | ||
|
|
7249a3c846 | ||
|
|
21fce10742 | ||
|
|
ea04c6d88f | ||
|
|
ce6127d87a | ||
|
|
646b1e36bf | ||
|
|
3eac379a13 | ||
|
|
88b6754c73 | ||
|
|
e0aa850d18 | ||
|
|
4a85837f2e | ||
|
|
9002116572 | ||
|
|
7517569ea4 | ||
|
|
cd86c78750 | ||
|
|
a0da295143 | ||
|
|
fd6ca8b081 | ||
|
|
3dea0a917e | ||
|
|
a94c333754 | ||
|
|
7fd482e3d6 | ||
|
|
162c1f1f31 | ||
|
|
f8affb2b6a | ||
|
|
738863ad38 | ||
|
|
59b3e65618 | ||
|
|
3840e50868 | ||
|
|
79339aefed | ||
|
|
df3a4c5916 | ||
|
|
2e21ae0da7 | ||
|
|
6992bfeef9 | ||
|
|
625d7e2e44 | ||
|
|
5af9082dc6 | ||
|
|
902d89b29e | ||
|
|
dfb0ea4202 | ||
|
|
890894b3ae | ||
|
|
dd95921eda | ||
|
|
9479a88393 | ||
|
|
3519611d8e | ||
|
|
1f04e0e655 | ||
|
|
074c1ff775 | ||
|
|
0e28e6a323 | ||
|
|
9f01e0f6ea | ||
|
|
805a03dfd2 | ||
|
|
99bf3a6c6a | ||
|
|
45ea8125d3 | ||
|
|
a606511a7e | ||
|
|
b5f215960f | ||
|
|
dc8ddc0dcc | ||
|
|
c84c098d2c | ||
|
|
f5a4071a81 | ||
|
|
ac5316e3ac | ||
|
|
2a2599de88 | ||
|
|
aac25fa480 | ||
|
|
999acc4273 | ||
|
|
5dcf3d8419 | ||
|
|
9c14853e73 | ||
|
|
5659122f1d | ||
|
|
3671222d7b | ||
|
|
bfd2883a4b | ||
|
|
67156d159f | ||
|
|
45c0891c3b | ||
|
|
0694372c61 | ||
|
|
2bbf4ebc9e | ||
|
|
818b7bb33f | ||
|
|
a265ecfade | ||
|
|
d52749c71a | ||
|
|
5eb5b93c0e | ||
|
|
7c6a39ec91 | ||
|
|
57c3a5c349 | ||
|
|
07c4c58ce4 | ||
|
|
6a07b468a3 | ||
|
|
d63fdf7d35 | ||
|
|
d6eaa9fd7a | ||
|
|
b7c4c28592 | ||
|
|
042a0f7986 | ||
|
|
5cc9e014b2 | ||
|
|
94e30485c4 | ||
|
|
c9e76ae5d4 | ||
|
|
568ed2f0f6 | ||
|
|
5237dc073a | ||
|
|
a09f754b48 | ||
|
|
64ad03ca60 | ||
|
|
b79b443a28 | ||
|
|
02148de9e2 | ||
|
|
d5bd93ebda | ||
|
|
76bbd94f5d | ||
|
|
b5546b4ab9 | ||
|
|
dca9389735 | ||
|
|
d3bebd94aa | ||
|
|
53c807bd5a | ||
|
|
dffbdf15f2 | ||
|
|
e09c35c177 | ||
|
|
0342d295e1 | ||
|
|
eb9849c411 | ||
|
|
93d48fae9d | ||
|
|
d90a7b2345 | ||
|
|
c2f6a364b8 | ||
|
|
bbadd92ffb | ||
|
|
6a7de24a04 | ||
|
|
67ccdd36fb | ||
|
|
2ddf55a60d | ||
|
|
57e7bc81d4 | ||
|
|
777f09598f | ||
|
|
81a9ef1df0 | ||
|
|
d063bc87a1 | ||
|
|
5fce08de65 | ||
|
|
c0db966afd | ||
|
|
9288995cad | ||
|
|
4d2abb4f65 | ||
|
|
60014b6530 | ||
|
|
3b57cab6b4 | ||
|
|
967467664b | ||
|
|
b87b5cffd8 | ||
|
|
bb44987af1 | ||
|
|
8d3ef2b224 | ||
|
|
5e409295f9 | ||
|
|
0b91a92554 | ||
|
|
7855df92c8 | ||
|
|
530c189f9c | ||
|
|
11309f89f0 | ||
|
|
396a987035 | ||
|
|
b7696bfb20 | ||
|
|
5cfbe2cf71 | ||
|
|
4e255286af | ||
|
|
f05fef9588 | ||
|
|
a257b5c54c | ||
|
|
5b9f7372fc | ||
|
|
a4c0a9b3a5 | ||
|
|
7d65b4c941 | ||
|
|
abd0ee7bce | ||
|
|
9e3eb20a04 | ||
|
|
6dc655c3b4 | ||
|
|
9f595a94fb | ||
|
|
5dc215a143 | ||
|
|
306b78ba5f | ||
|
|
bccb646a07 | ||
|
|
4a5dc8cdd6 | ||
|
|
53a96af844 | ||
|
|
accb705d8b | ||
|
|
1793abce4f | ||
|
|
8bfed0b60c | ||
|
|
016c1de2ef | ||
|
|
c270f31365 | ||
|
|
f9e06acfc7 | ||
|
|
901558b293 | ||
|
|
52a751507a | ||
|
|
533b9f969d | ||
|
|
5de7928bc0 | ||
|
|
aad9b07f86 | ||
|
|
3e2c401253 | ||
|
|
762e63d042 | ||
|
|
c09cf36345 | ||
|
|
ec6d40a51c | ||
|
|
47c2c61626 | ||
|
|
73c941f6c5 | ||
|
|
685edb5f76 | ||
|
|
5987b6dcb9 | ||
|
|
cb029e0bb0 | ||
|
|
553ec35947 | ||
|
|
f93940bfa9 | ||
|
|
486f93eb28 | ||
|
|
462db36fef | ||
|
|
485f7f45e8 | ||
|
|
a446d8a98c | ||
|
|
b4a31fc578 | ||
|
|
22321c22cc | ||
|
|
926627b49c | ||
|
|
a8eeba9c5f | ||
|
|
e4591c27c0 | ||
|
|
40dedec602 | ||
|
|
3a65b5ca70 | ||
|
|
dbeb82861f | ||
|
|
e43c35ab2d | ||
|
|
b4c4fdefe3 | ||
|
|
965dd7c557 | ||
|
|
9a921f2c8e | ||
|
|
aaae3244a8 | ||
|
|
40ff100900 | ||
|
|
1b62b2309f | ||
|
|
9d57251aea | ||
|
|
4419c236e2 | ||
|
|
1731a2534c | ||
|
|
ec0edf47b1 | ||
|
|
57c69738e3 | ||
|
|
fb1f258b2b | ||
|
|
d419dd0c05 | ||
|
|
65960aa3f7 | ||
|
|
a25afe2834 | ||
|
|
4cdfa3bddb | ||
|
|
9e7bef9fa7 | ||
|
|
68a1b1f91f | ||
|
|
1659ca532d | ||
|
|
8ea16daae4 | ||
|
|
5bd89acf9a | ||
|
|
2b8db74be4 | ||
|
|
d7f9a7114d | ||
|
|
f7a59eb86e | ||
|
|
37eef965ad | ||
|
|
b706430e66 | ||
|
|
5012aa5cb0 | ||
|
|
1c5f7adf4e | ||
|
|
ff364e3913 | ||
|
|
0e2a4605ff | ||
|
|
ca5b9ce0d3 | ||
|
|
953196ec21 | ||
|
|
b5be3d5ac3 | ||
|
|
5d9e8287d3 | ||
|
|
dc291708ae | ||
|
|
257e82fe4e | ||
|
|
ab6d4d645e | ||
|
|
58ebd84326 | ||
|
|
76b24dafed | ||
|
|
431f563ff6 | ||
|
|
e308e610c6 | ||
|
|
5e77cbd185 | ||
|
|
2dbc7ff4b7 | ||
|
|
49a6c5776d | ||
|
|
98f6001c9c | ||
|
|
f877614e7f | ||
|
|
170e1e87c7 | ||
|
|
e1feba5c86 | ||
|
|
9bf52b7966 | ||
|
|
3bc61a3564 | ||
|
|
ce38a93177 | ||
|
|
92fbf468f2 | ||
|
|
e09ec4a6f3 | ||
|
|
db63e0c829 | ||
|
|
8ed88d4a58 | ||
|
|
d098ada777 | ||
|
|
1add999c5a | ||
|
|
fad217837f | ||
|
|
983af1af7b | ||
|
|
bcf2c4e9b6 | ||
|
|
c72f2fd546 | ||
|
|
f54864a476 | ||
|
|
fe1ff456c6 | ||
|
|
ec25ead5ac | ||
|
|
e8277cb67c | ||
|
|
d2ba94e1bf | ||
|
|
9a4ed82399 | ||
|
|
b5136d01aa | ||
|
|
d3e05090ea | ||
|
|
7e75ca7af9 | ||
|
|
6616b5775f | ||
|
|
69b82d4c59 | ||
|
|
6b9709677a | ||
|
|
da0fb37a20 | ||
|
|
a4e9c82c84 | ||
|
|
de86bedb80 | ||
|
|
9111c6df90 | ||
|
|
751f6bddb1 | ||
|
|
c9a61de0a1 | ||
|
|
01fb46d903 | ||
|
|
d26f61c9fe | ||
|
|
a47a144312 | ||
|
|
69cf4f99d1 | ||
|
|
e6c757c187 | ||
|
|
a36b0e2f3f | ||
|
|
1a7c6cd96c | ||
|
|
ba3e538402 | ||
|
|
b2cd08aa65 | ||
|
|
06dcd25a16 | ||
|
|
28675eee33 | ||
|
|
84561cbc41 | ||
|
|
4e48c881aa | ||
|
|
af8cd0414b | ||
|
|
f54076da29 | ||
|
|
1d0eb97592 | ||
|
|
57f1c268ef | ||
|
|
01402e4f96 | ||
|
|
6137a643d8 | ||
|
|
1badfe3aff | ||
|
|
a549104fe1 | ||
|
|
2aab2ddc55 | ||
|
|
42e01362a5 | ||
|
|
c3cf24ba25 | ||
|
|
7809fb6a9b | ||
|
|
144fc2a443 | ||
|
|
c67e005b2c | ||
|
|
1c6913eec2 | ||
|
|
fb5c4a1375 | ||
|
|
60b8392478 | ||
|
|
7145fb96dd | ||
|
|
37d94aca6d | ||
|
|
9b697edfca | ||
|
|
22e30be946 | ||
|
|
bc9d35b85f | ||
|
|
4dfb6e4983 | ||
|
|
09d78ab5ad | ||
|
|
b2ebdb7ef0 | ||
|
|
83d6a30b2e | ||
|
|
19dee6d22a | ||
|
|
afd27630fb | ||
|
|
cad1f1da1d | ||
|
|
cd62bd86fd | ||
|
|
79c3bc9eca | ||
|
|
10439eea4b | ||
|
|
75cc866e72 | ||
|
|
8b2ca6c571 | ||
|
|
52db73e8e3 | ||
|
|
79d15ec91c | ||
|
|
5af91df2b9 | ||
|
|
89a85c3d8c | ||
|
|
e44c6391b1 | ||
|
|
99d3234855 | ||
|
|
32cc5123f5 | ||
|
|
93415175bb | ||
|
|
f04bb69dbc | ||
|
|
9f8c9940bd | ||
|
|
496f527dff | ||
|
|
385e6f58a8 | ||
|
|
c8c37ad628 | ||
|
|
cc57732e24 | ||
|
|
6011df8952 | ||
|
|
08e494aba5 | ||
|
|
77c428e4c7 | ||
|
|
c22a2b93fa | ||
|
|
7f84363bf4 | ||
|
|
0980c3a270 | ||
|
|
7cec3aa27c | ||
|
|
1ddb39f6d0 | ||
|
|
10d2e38315 | ||
|
|
5299690cb7 | ||
|
|
98c1dca7a8 | ||
|
|
54c022d58a | ||
|
|
77d40ddc7d | ||
|
|
092841ca5e | ||
|
|
70238a613d | ||
|
|
5b8d373527 | ||
|
|
4e3664b26f | ||
|
|
76f5cc368b | ||
|
|
2f4cd21a14 | ||
|
|
d369aa761a | ||
|
|
d795806e3d | ||
|
|
fd36782bae | ||
|
|
ed4573db57 | ||
|
|
78373a6483 | ||
|
|
8455c35bec | ||
|
|
00887a2f3f | ||
|
|
f1ca7543fa | ||
|
|
bb72b24ba9 | ||
|
|
322a27d992 | ||
|
|
a3b516110b | ||
|
|
d45a80ed06 | ||
|
|
09b46d22af | ||
|
|
b157afac13 | ||
|
|
edaf5c8167 | ||
|
|
1d6c9e3d94 | ||
|
|
ddbc96206f | ||
|
|
cee5f4e275 | ||
|
|
03a1ffc59b | ||
|
|
6e921a0192 | ||
|
|
99eb48c27f | ||
|
|
06dbfe52d0 | ||
|
|
b516de119c | ||
|
|
dcb2087f4b | ||
|
|
7de94f3632 | ||
|
|
909e2304c1 | ||
|
|
ae0b854314 | ||
|
|
6a6054afee | ||
|
|
3377e90b81 | ||
|
|
342c7f6510 | ||
|
|
982fba167a | ||
|
|
8026e64d7c | ||
|
|
ebbfd5a6c7 | ||
|
|
356077541c | ||
|
|
0b9a22b089 | ||
|
|
cce6f60b70 | ||
|
|
d57dc5d0cd | ||
|
|
6088f5eef5 | ||
|
|
5c96b11479 | ||
|
|
afda849f3e | ||
|
|
f2f769b34a | ||
|
|
45558f3087 | ||
|
|
c10b643af9 | ||
|
|
569dd2d6b7 | ||
|
|
95ac5c0183 | ||
|
|
40995b6d32 | ||
|
|
f64c029bd1 | ||
|
|
a050c0cd05 | ||
|
|
0c121468e0 | ||
|
|
af277651f8 | ||
|
|
ca90826478 | ||
|
|
dd4789af4e | ||
|
|
ca49b6e7e2 | ||
|
|
fdbe293483 | ||
|
|
aa67b46f6f | ||
|
|
9f21a97d39 | ||
|
|
49158fad48 | ||
|
|
dff8e5221b | ||
|
|
dbfa0aa22c | ||
|
|
ec4464d65f | ||
|
|
6c8dffd521 | ||
|
|
09763012fc | ||
|
|
aa5b970102 | ||
|
|
80912045d7 | ||
|
|
dbda31f6d5 | ||
|
|
24219dd8f7 | ||
|
|
77d0cf1573 | ||
|
|
1ea534e400 | ||
|
|
a6ba8fa69c | ||
|
|
a6c1ce86d8 | ||
|
|
fbb07e16cb | ||
|
|
1387308f48 | ||
|
|
48d371eddb | ||
|
|
c45c11574c | ||
|
|
14eb103338 | ||
|
|
2bdb258a39 | ||
|
|
5b323526b6 | ||
|
|
9d434c9403 | ||
|
|
e4103137ef | ||
|
|
d9d4cc9004 | ||
|
|
67baa2c737 | ||
|
|
f9c504fcde | ||
|
|
4b25d04326 | ||
|
|
4e3eb44e69 | ||
|
|
6c84d25024 | ||
|
|
c4dc4135e1 | ||
|
|
78bbdf108b | ||
|
|
47397fd736 | ||
|
|
2b62d2d636 | ||
|
|
350f99baab | ||
|
|
1245385371 | ||
|
|
c86852eb21 | ||
|
|
ad635d2eff | ||
|
|
cf0e2b85dd | ||
|
|
b9e7f1c628 | ||
|
|
079d65acea | ||
|
|
162737a473 | ||
|
|
d074c5b7c8 | ||
|
|
d6ae0c142e | ||
|
|
58182a344d | ||
|
|
1a1f3d6b4e | ||
|
|
71589d212f | ||
|
|
9364a40fd2 | ||
|
|
7ead8f9154 | ||
|
|
09ac47b35f | ||
|
|
404393d6fe | ||
|
|
de5a26830d | ||
|
|
c0b0ce0c16 | ||
|
|
88e27d9017 | ||
|
|
a37ba6dba4 | ||
|
|
f38c0d510e | ||
|
|
be9fa9a606 | ||
|
|
2bc6e728a3 | ||
|
|
4e4a0d1e28 | ||
|
|
c860aa1531 | ||
|
|
39c73cbbbd | ||
|
|
ca7332f597 | ||
|
|
eafedeb12a | ||
|
|
ce93de7fc6 | ||
|
|
03f339b3a3 | ||
|
|
6cfeeea1ba | ||
|
|
ccbdaabe74 | ||
|
|
dd22c78d11 | ||
|
|
1a732accdd | ||
|
|
59d663ee6e | ||
|
|
2442a96c27 | ||
|
|
f69de99501 | ||
|
|
8ce9238f7a | ||
|
|
2fe697486d | ||
|
|
0bd25dc254 | ||
|
|
ed6af953af | ||
|
|
84f65c3f77 | ||
|
|
77c024fcdd | ||
|
|
27570138e7 | ||
|
|
96f84b2b99 | ||
|
|
28bee6d1aa | ||
|
|
c9d68ddd5c | ||
|
|
ac6dddc895 | ||
|
|
f7e5dc7637 | ||
|
|
1a5f431485 | ||
|
|
faa04755e2 | ||
|
|
76584161a6 | ||
|
|
103b121868 | ||
|
|
76a6b3cea6 | ||
|
|
87e7fe6e37 | ||
|
|
c782489973 | ||
|
|
c9dbc1458c | ||
|
|
d4a33611f0 | ||
|
|
f028b7ab6e | ||
|
|
86a9df761a | ||
|
|
e3359fb62d | ||
|
|
84065767d1 | ||
|
|
e7c60f573d | ||
|
|
a59847309a | ||
|
|
3c8ac20881 | ||
|
|
6394fdbc97 | ||
|
|
a7a673d437 | ||
|
|
196d7bb8cf | ||
|
|
ce5538e615 | ||
|
|
6819b0ce5b | ||
|
|
91eb35d7cb | ||
|
|
256615644c | ||
|
|
aff6cfb032 | ||
|
|
a5d8c383c4 | ||
|
|
81df40e2b0 | ||
|
|
3e6ddda54e | ||
|
|
76c6b92910 | ||
|
|
f7db56a6b3 | ||
|
|
507645a60f | ||
|
|
7c4406265a | ||
|
|
4e6bd48416 | ||
|
|
255b1efe81 | ||
|
|
afa5093d7a | ||
|
|
a43368675e | ||
|
|
39d76a24db | ||
|
|
3e17a97422 | ||
|
|
e6e78f86bd | ||
|
|
a72fe28d7a | ||
|
|
fa7ff1d996 | ||
|
|
2cc910f4b1 | ||
|
|
b686e4d8db | ||
|
|
07493e5b3e | ||
|
|
477073da75 | ||
|
|
b59bc45ef1 | ||
|
|
da055795c7 | ||
|
|
3cd17a2b9f | ||
|
|
d689400b3b | ||
|
|
a1f024eed8 | ||
|
|
07b6358fff | ||
|
|
6fa73f7f6a | ||
|
|
3c46b40cee | ||
|
|
4c9810a10e | ||
|
|
83e9fca6a2 | ||
|
|
fc9313f7ef | ||
|
|
278f32285a | ||
|
|
8c360908ef | ||
|
|
82c5337fcf | ||
|
|
7950f9ab38 | ||
|
|
66eeb41e56 | ||
|
|
1bef707cd1 | ||
|
|
2125a4123d | ||
|
|
27516dee6a | ||
|
|
40c9e5356e | ||
|
|
2521920376 | ||
|
|
16eb8315ee | ||
|
|
2c3d65b461 | ||
|
|
7e938f4f13 | ||
|
|
c5c4cf0284 | ||
|
|
68c38ac047 | ||
|
|
f5a6fa8be1 | ||
|
|
3c751918fd | ||
|
|
a3c8760b3f | ||
|
|
7bceaf74be | ||
|
|
750f06327a | ||
|
|
98bffdb9d3 | ||
|
|
174b0f7c01 | ||
|
|
807edc9f47 | ||
|
|
9b0f67fbde | ||
|
|
27390647ff | ||
|
|
d9f6a6bf99 | ||
|
|
67be4f863f | ||
|
|
c960bd2845 | ||
|
|
447fb68085 | ||
|
|
750a7c9797 | ||
|
|
5679ab0f86 | ||
|
|
2761dcbc48 | ||
|
|
3a00d96571 | ||
|
|
c86ad896b8 | ||
|
|
9c1d8747be | ||
|
|
4a003114bd | ||
|
|
dcc3f14b1f | ||
|
|
7687d5ea48 | ||
|
|
27cc3c838a | ||
|
|
619cb91839 | ||
|
|
5e5e130d4e | ||
|
|
f6ac23cc58 | ||
|
|
244b6437b2 | ||
|
|
5dc271b201 | ||
|
|
531cc3e1ce | ||
|
|
ed7c3cb339 | ||
|
|
3b7e1a2610 | ||
|
|
16e11ed801 | ||
|
|
32a7bf4dbb | ||
|
|
856c99dc22 | ||
|
|
a50b00b3c2 | ||
|
|
1df8b1063b | ||
|
|
32cd4364f6 | ||
|
|
0828a842a5 | ||
|
|
f63a527a01 | ||
|
|
254a9ecc25 | ||
|
|
a518907b09 | ||
|
|
cd85699151 | ||
|
|
f49dfbd459 | ||
|
|
3ed70bb751 | ||
|
|
b4b1065737 | ||
|
|
7267c3c04e | ||
|
|
6ac4d2dd59 | ||
|
|
03abd5d277 | ||
|
|
66bb0db08b | ||
|
|
56ae579e83 | ||
|
|
add1915b8a | ||
|
|
18ef4af8d0 | ||
|
|
3c6788212f | ||
|
|
dbd8b1bc19 | ||
|
|
d135f1c110 | ||
|
|
c11cacbb58 | ||
|
|
d8d6490fb4 | ||
|
|
2341f53ac0 | ||
|
|
94e8ffadd2 | ||
|
|
5186c402e7 | ||
|
|
4403447d53 | ||
|
|
9a8a2bfebc | ||
|
|
a907a34f6e | ||
|
|
03b15f1dba | ||
|
|
a48b915d90 | ||
|
|
e8227baa50 | ||
|
|
4d525dee48 | ||
|
|
c38e02072e | ||
|
|
2d84bd65fe | ||
|
|
1d8eaeb8af | ||
|
|
33ed851477 | ||
|
|
a571271f6a | ||
|
|
43445f5945 | ||
|
|
3c098db35e | ||
|
|
c72072fbfb | ||
|
|
10e2c7ec95 | ||
|
|
64493ca578 | ||
|
|
5f008dcae5 | ||
|
|
80c06c689c | ||
|
|
b19070fa4b | ||
|
|
53984f7abc | ||
|
|
38620a0cda | ||
|
|
b09927bb53 | ||
|
|
11b9f7915b | ||
|
|
3ddb520693 | ||
|
|
ffc54c699d | ||
|
|
91b516d739 | ||
|
|
25a9948020 | ||
|
|
f0f420ebe2 | ||
|
|
312812dd8b | ||
|
|
e0d404456b | ||
|
|
439fc18860 | ||
|
|
774ab06206 | ||
|
|
f484db8f0e | ||
|
|
4af3c4f720 | ||
|
|
a020a32d8a | ||
|
|
1ac2ae3443 | ||
|
|
2fce7db132 | ||
|
|
6e49911e1c | ||
|
|
4215a16285 | ||
|
|
65ff4fe10e | ||
|
|
5b7675e389 | ||
|
|
3019744035 | ||
|
|
21ab630380 | ||
|
|
564ff12db0 | ||
|
|
6c919e698f | ||
|
|
5d644815fa | ||
|
|
8dfa0f2f65 | ||
|
|
f9484acbfa | ||
|
|
d0c0247086 | ||
|
|
b116619af1 | ||
|
|
a3d760156f | ||
|
|
6e194ad6ef | ||
|
|
1e2a21b69f | ||
|
|
e90fe96b4e | ||
|
|
4774ed508a | ||
|
|
8f4a4d4c47 | ||
|
|
a83bbe2332 | ||
|
|
e5b93d3275 | ||
|
|
1c024f58af | ||
|
|
fa86148df0 | ||
|
|
7c6bbb97ea | ||
|
|
a5af501da4 | ||
|
|
f23cfb5594 | ||
|
|
67a12dc007 | ||
|
|
5783b3a576 | ||
|
|
7bc43039bd | ||
|
|
23e2316c36 | ||
|
|
9e9c8f5724 | ||
|
|
11772dbc46 | ||
|
|
c12df5d776 | ||
|
|
b57ca143e6 | ||
|
|
b3e16bd4fa | ||
|
|
18d5035877 | ||
|
|
d6db50fcc7 | ||
|
|
84d9fa3bd7 | ||
|
|
b08eb3a201 | ||
|
|
c74c317922 | ||
|
|
9edc6249ca | ||
|
|
4fbcb79889 | ||
|
|
68fd5bc67e | ||
|
|
882d047bb5 | ||
|
|
5c070c8f03 | ||
|
|
854882d612 | ||
|
|
b078c0ee7e | ||
|
|
080b16a33d | ||
|
|
6a1cf9827c | ||
|
|
23e7b14eae | ||
|
|
2a5cf83f50 | ||
|
|
5dcb68cdf6 | ||
|
|
fedeca107a | ||
|
|
4fef19c7bc | ||
|
|
8c953b0c4e | ||
|
|
949544874f | ||
|
|
237407010a | ||
|
|
64e48816c7 | ||
|
|
6b76b3e729 | ||
|
|
4912280193 | ||
|
|
d4e72ad2cf | ||
|
|
711526fc6c | ||
|
|
4be428fce7 | ||
|
|
ea226806a0 | ||
|
|
bc77daf2ce | ||
|
|
acead56bd5 | ||
|
|
fd08c55b79 | ||
|
|
0c342c4750 | ||
|
|
da6986e58c | ||
|
|
2f5fbc1f0e | ||
|
|
e79c76cd35 | ||
|
|
6edafd8965 | ||
|
|
204ff5d45f | ||
|
|
591eb94515 | ||
|
|
0f3de627c5 | ||
|
|
b2699d8a03 | ||
|
|
769a770cf1 | ||
|
|
2d96e8ac4d | ||
|
|
354cacdcae | ||
|
|
d999dd05d1 | ||
|
|
81572c6a84 | ||
|
|
8165ac196f | ||
|
|
41c95247ec | ||
|
|
2eb3360e8c | ||
|
|
fcd07902b0 | ||
|
|
71f94cad97 | ||
|
|
05277aa708 | ||
|
|
9f74471d22 | ||
|
|
1c8487a7e7 | ||
|
|
3c8612b6fd | ||
|
|
f28892c526 | ||
|
|
24b7cf261c | ||
|
|
ef69e6d54b | ||
|
|
ca31a279fa | ||
|
|
e50c4c4787 | ||
|
|
3ecddda8dd | ||
|
|
af77f835a5 | ||
|
|
6de2016aa3 | ||
|
|
f1e72c1616 | ||
|
|
7af3eb638b | ||
|
|
363e5f088c | ||
|
|
5b1e3ef574 | ||
|
|
d607323731 | ||
|
|
31f595a3f8 | ||
|
|
9a27805349 | ||
|
|
477cdbb711 | ||
|
|
62b39fdd10 | ||
|
|
f806cc8b4b | ||
|
|
b6108001e4 | ||
|
|
56f33a8a5f | ||
|
|
1e91ad6e23 | ||
|
|
9032de4b26 | ||
|
|
553fcb5156 | ||
|
|
378295e1cc | ||
|
|
ff95c6235f | ||
|
|
d398a13899 | ||
|
|
10b300e573 | ||
|
|
e95c0ef3a8 | ||
|
|
3b09adb360 | ||
|
|
d2380608e1 | ||
|
|
37188a0832 | ||
|
|
3134fd75e8 | ||
|
|
861f4a0578 | ||
|
|
a82c512472 | ||
|
|
10392d9719 | ||
|
|
b7acc66153 | ||
|
|
6249cabcba | ||
|
|
84f2fd106d | ||
|
|
45cc68d3e4 | ||
|
|
7fd75c7742 | ||
|
|
9522b11042 | ||
|
|
c874c4ac73 | ||
|
|
907ef8fa15 | ||
|
|
bc93153c40 | ||
|
|
6964829699 | ||
|
|
62e59608b0 | ||
|
|
9507b3f3aa | ||
|
|
1d187abe10 | ||
|
|
0464cb8929 | ||
|
|
f410d94f80 | ||
|
|
dee3c11203 | ||
|
|
06e4b0a798 | ||
|
|
2fd55a49cb | ||
|
|
80d7224dcf | ||
|
|
9d03b1b9b4 | ||
|
|
cecdf553f3 | ||
|
|
54e6fbc042 | ||
|
|
9c098d3471 | ||
|
|
bb98ed6633 | ||
|
|
59dace572a | ||
|
|
735cf36a5b | ||
|
|
90b0f50b8f | ||
|
|
e731c07b77 | ||
|
|
2c75635e95 | ||
|
|
1f031695c2 | ||
|
|
fb279212a9 | ||
|
|
45869523d0 | ||
|
|
a753926f22 | ||
|
|
dc874ff53a | ||
|
|
3ef6865708 | ||
|
|
7f1989f9f2 | ||
|
|
97e338c760 | ||
|
|
101679c17d | ||
|
|
bc784c356e | ||
|
|
556cc57d8b | ||
|
|
eef6e96a93 | ||
|
|
56d237af7f | ||
|
|
e5d1902d2a | ||
|
|
a9a203678e | ||
|
|
7f6237cc63 | ||
|
|
5468e691ca | ||
|
|
67cbbc3522 | ||
|
|
504da54c11 | ||
|
|
cdda2ef5c8 | ||
|
|
f405f9eb4b | ||
|
|
634f71835a | ||
|
|
49bfb01fac | ||
|
|
ad8f7fdcab | ||
|
|
f82ec81062 | ||
|
|
03b0842a01 | ||
|
|
13e5cb5cc8 | ||
|
|
f18cdaf4d8 | ||
|
|
5b3bca1426 | ||
|
|
d812e9d43c | ||
|
|
fa1071b221 | ||
|
|
e48c2c6c0b | ||
|
|
bddd4100c0 | ||
|
|
70d8df2e95 | ||
|
|
08b3dd0173 | ||
|
|
6723a7c4e1 | ||
|
|
394575e4f7 | ||
|
|
effc33d0d2 | ||
|
|
7af4c337c6 | ||
|
|
4f222d7adf | ||
|
|
00f16812e4 | ||
|
|
0efaf7efe8 | ||
|
|
55643f0632 | ||
|
|
36f4723f6e | ||
|
|
03bc698936 | ||
|
|
0c1dc73422 | ||
|
|
c31537081b | ||
|
|
d13067abb3 | ||
|
|
64da32b5f9 | ||
|
|
3990fc6ab2 | ||
|
|
e4071bd305 | ||
|
|
8dda26c227 | ||
|
|
40d7f2a89e | ||
|
|
13b717e2da | ||
|
|
5fcfd3ad84 | ||
|
|
324a7b5443 | ||
|
|
491ae8f72c | ||
|
|
259247892f | ||
|
|
caeda0ef64 | ||
|
|
df35c535e4 | ||
|
|
f93b9e0ed0 | ||
|
|
48a3372cf2 | ||
|
|
d84fd72428 | ||
|
|
e8cb386962 | ||
|
|
5ac726703c | ||
|
|
688649a799 | ||
|
|
c5359ade3e | ||
|
|
4e60dedc1b | ||
|
|
221d74f83a | ||
|
|
fbbb3d6415 | ||
|
|
8297019011 | ||
|
|
61715dcff3 | ||
|
|
32b822ee99 | ||
|
|
e6c2e0ad80 | ||
|
|
1314427dc5 | ||
|
|
bf499a45f7 | ||
|
|
b955e22628 | ||
|
|
1b222ff5fd | ||
|
|
f0510e703f | ||
|
|
cbe3956e15 | ||
|
|
4588e9da8d | ||
|
|
5445890fdf | ||
|
|
9b49f77f86 | ||
|
|
566c8fb786 | ||
|
|
b36150c213 | ||
|
|
809070d2ad | ||
|
|
f4339dc031 | ||
|
|
f3b37d24b0 | ||
|
|
4c8348caa7 | ||
|
|
b9e7c102ea | ||
|
|
69d9fa89b7 | ||
|
|
6f3f5a5ec1 | ||
|
|
5ecfeca90a | ||
|
|
00e0570fd4 | ||
|
|
5a5b94f3af | ||
|
|
34f00d9b33 | ||
|
|
4cabc5b368 | ||
|
|
4045125422 | ||
|
|
d7393af76f | ||
|
|
ad41386b27 | ||
|
|
62d17ea20c | ||
|
|
c4954731d0 | ||
|
|
647723d3f0 | ||
|
|
51c500e22c | ||
|
|
f6fc13c1f2 | ||
|
|
0009a7a042 | ||
|
|
a3d1aa28e7 | ||
|
|
9f53eb9b76 | ||
|
|
f53a205ff3 | ||
|
|
d08517c3df | ||
|
|
d7398a44a1 | ||
|
|
9acfc0cb88 | ||
|
|
1b3d21523a | ||
|
|
1d407d1326 | ||
|
|
013346cead | ||
|
|
5abaabc9da | ||
|
|
32481312c3 | ||
|
|
bdc9eb37d3 | ||
|
|
e0afcbc02b | ||
|
|
cd56a6a98d | ||
|
|
9d85893bbb | ||
|
|
9e8a70225f | ||
|
|
96ec795d5e | ||
|
|
65b796070d | ||
|
|
32994812e5 | ||
|
|
66ff9d63a3 | ||
|
|
b2a63d4996 | ||
|
|
f9f37b7f2a | ||
|
|
7bdd9dd38a | ||
|
|
1e8aae0a89 | ||
|
|
cf668e9dc2 | ||
|
|
2e91c8700f | ||
|
|
9d14627daa | ||
|
|
73b8283748 | ||
|
|
edeaaa2e63 | ||
|
|
d26dd8fc39 | ||
|
|
34640ea735 | ||
|
|
46a2e21ef0 | ||
|
|
508af53e72 | ||
|
|
5f7440608c | ||
|
|
0d1aa38a26 | ||
|
|
929f8c148a | ||
|
|
92db1f5a04 | ||
|
|
e66b5ce0bf | ||
|
|
1e17150e9f | ||
|
|
792902de3d | ||
|
|
04d78c3dd5 | ||
|
|
5c8d5bfb84 | ||
|
|
99bff31869 | ||
|
|
d949119fb0 | ||
|
|
e7b737ece5 | ||
|
|
fb8ddac2e8 |
@@ -33,7 +33,7 @@
|
|||||||
"GitHub.vscode-pull-request-github",
|
"GitHub.vscode-pull-request-github",
|
||||||
"GitHub.copilot"
|
"GitHub.copilot"
|
||||||
],
|
],
|
||||||
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.json
|
// Please keep this file in sync with settings in home-assistant/.vscode/settings.default.jsonc
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.experiments.optOutFrom": ["pythonTestAdapter"],
|
"python.experiments.optOutFrom": ["pythonTestAdapter"],
|
||||||
"python.defaultInterpreterPath": "/home/vscode/.local/ha-venv/bin/python",
|
"python.defaultInterpreterPath": "/home/vscode/.local/ha-venv/bin/python",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"python.terminal.activateEnvInCurrentTerminal": true,
|
"python.terminal.activateEnvInCurrentTerminal": true,
|
||||||
"python.testing.pytestArgs": ["--no-cov"],
|
"python.testing.pytestArgs": ["--no-cov"],
|
||||||
"pylint.importStrategy": "fromEnvironment",
|
"pylint.importStrategy": "fromEnvironment",
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
"editor.formatOnPaste": false,
|
"editor.formatOnPaste": false,
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.formatOnType": true,
|
"editor.formatOnType": true,
|
||||||
@@ -62,6 +63,9 @@
|
|||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.defaultFormatter": "charliermarsh.ruff"
|
"editor.defaultFormatter": "charliermarsh.ruff"
|
||||||
},
|
},
|
||||||
|
"[json][jsonc][yaml]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": ["homeassistant/components/*/manifest.json"],
|
"fileMatch": ["homeassistant/components/*/manifest.json"],
|
||||||
|
|||||||
1
.github/copilot-instructions.md
vendored
1
.github/copilot-instructions.md
vendored
@@ -74,6 +74,7 @@ rules:
|
|||||||
- **Formatting**: Ruff
|
- **Formatting**: Ruff
|
||||||
- **Linting**: PyLint and Ruff
|
- **Linting**: PyLint and Ruff
|
||||||
- **Type Checking**: MyPy
|
- **Type Checking**: MyPy
|
||||||
|
- **Lint/Type/Format Fixes**: Always prefer addressing the underlying issue (e.g., import the typed source, update shared stubs, align with Ruff expectations, or correct formatting at the source) before disabling a rule, adding `# type: ignore`, or skipping a formatter. Treat suppressions and `noqa` comments as a last resort once no compliant fix exists
|
||||||
- **Testing**: pytest with plain functions and fixtures
|
- **Testing**: pytest with plain functions and fixtures
|
||||||
- **Language**: American English for all code, comments, and documentation (use sentence case, including titles)
|
- **Language**: American English for all code, comments, and documentation (use sentence case, including titles)
|
||||||
|
|
||||||
|
|||||||
66
.github/workflows/builder.yml
vendored
66
.github/workflows/builder.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
publish: ${{ steps.version.outputs.publish }}
|
publish: ${{ steps.version.outputs.publish }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
@@ -90,7 +90,7 @@ jobs:
|
|||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Download nightly wheels of frontend
|
- name: Download nightly wheels of frontend
|
||||||
if: needs.init.outputs.channel == 'dev'
|
if: needs.init.outputs.channel == 'dev'
|
||||||
@@ -162,20 +162,8 @@ jobs:
|
|||||||
sed -i "s|home-assistant-intents==.*||" requirements_all.txt
|
sed -i "s|home-assistant-intents==.*||" requirements_all.txt
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Adjustments for armhf
|
|
||||||
if: matrix.arch == 'armhf'
|
|
||||||
run: |
|
|
||||||
# Pandas has issues building on armhf, it is expected they
|
|
||||||
# will drop the platform in the near future (they consider it
|
|
||||||
# "flimsy" on 386). The following packages depend on pandas,
|
|
||||||
# so we comment them out.
|
|
||||||
sed -i "s|env-canada|# env-canada|g" requirements_all.txt
|
|
||||||
sed -i "s|noaa-coops|# noaa-coops|g" requirements_all.txt
|
|
||||||
sed -i "s|pyezviz|# pyezviz|g" requirements_all.txt
|
|
||||||
sed -i "s|pykrakenapi|# pykrakenapi|g" requirements_all.txt
|
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
|
|
||||||
@@ -226,24 +214,16 @@ jobs:
|
|||||||
- odroid-c4
|
- odroid-c4
|
||||||
- odroid-m1
|
- odroid-m1
|
||||||
- odroid-n2
|
- odroid-n2
|
||||||
- odroid-xu
|
|
||||||
- qemuarm
|
|
||||||
- qemuarm-64
|
- qemuarm-64
|
||||||
- qemux86
|
|
||||||
- qemux86-64
|
- qemux86-64
|
||||||
- raspberrypi
|
|
||||||
- raspberrypi2
|
|
||||||
- raspberrypi3
|
|
||||||
- raspberrypi3-64
|
- raspberrypi3-64
|
||||||
- raspberrypi4
|
|
||||||
- raspberrypi4-64
|
- raspberrypi4-64
|
||||||
- raspberrypi5-64
|
- raspberrypi5-64
|
||||||
- tinker
|
|
||||||
- yellow
|
- yellow
|
||||||
- green
|
- green
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set build additional args
|
- name: Set build additional args
|
||||||
run: |
|
run: |
|
||||||
@@ -281,7 +261,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Initialize git
|
- name: Initialize git
|
||||||
uses: home-assistant/actions/helpers/git-init@master
|
uses: home-assistant/actions/helpers/git-init@master
|
||||||
@@ -297,6 +277,7 @@ jobs:
|
|||||||
key-description: "Home Assistant Core"
|
key-description: "Home Assistant Core"
|
||||||
version: ${{ needs.init.outputs.version }}
|
version: ${{ needs.init.outputs.version }}
|
||||||
channel: ${{ needs.init.outputs.channel }}
|
channel: ${{ needs.init.outputs.channel }}
|
||||||
|
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
|
||||||
|
|
||||||
- name: Update version file (stable -> beta)
|
- name: Update version file (stable -> beta)
|
||||||
if: needs.init.outputs.channel == 'stable'
|
if: needs.init.outputs.channel == 'stable'
|
||||||
@@ -306,6 +287,7 @@ jobs:
|
|||||||
key-description: "Home Assistant Core"
|
key-description: "Home Assistant Core"
|
||||||
version: ${{ needs.init.outputs.version }}
|
version: ${{ needs.init.outputs.version }}
|
||||||
channel: beta
|
channel: beta
|
||||||
|
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
|
||||||
|
|
||||||
publish_container:
|
publish_container:
|
||||||
name: Publish meta container for ${{ matrix.registry }}
|
name: Publish meta container for ${{ matrix.registry }}
|
||||||
@@ -323,7 +305,7 @@ jobs:
|
|||||||
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Install Cosign
|
- name: Install Cosign
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||||
@@ -357,27 +339,12 @@ jobs:
|
|||||||
|
|
||||||
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
docker manifest create "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
"${registry}/i386-homeassistant:${tag_r}" \
|
|
||||||
"${registry}/armhf-homeassistant:${tag_r}" \
|
|
||||||
"${registry}/armv7-homeassistant:${tag_r}" \
|
|
||||||
"${registry}/aarch64-homeassistant:${tag_r}"
|
"${registry}/aarch64-homeassistant:${tag_r}"
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/amd64-homeassistant:${tag_r}" \
|
"${registry}/amd64-homeassistant:${tag_r}" \
|
||||||
--os linux --arch amd64
|
--os linux --arch amd64
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
|
||||||
"${registry}/i386-homeassistant:${tag_r}" \
|
|
||||||
--os linux --arch 386
|
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
|
||||||
"${registry}/armhf-homeassistant:${tag_r}" \
|
|
||||||
--os linux --arch arm --variant=v6
|
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
|
||||||
"${registry}/armv7-homeassistant:${tag_r}" \
|
|
||||||
--os linux --arch arm --variant=v7
|
|
||||||
|
|
||||||
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
docker manifest annotate "${registry}/home-assistant:${tag_l}" \
|
||||||
"${registry}/aarch64-homeassistant:${tag_r}" \
|
"${registry}/aarch64-homeassistant:${tag_r}" \
|
||||||
--os linux --arch arm64 --variant=v8
|
--os linux --arch arm64 --variant=v8
|
||||||
@@ -405,23 +372,14 @@ jobs:
|
|||||||
|
|
||||||
# Pull images from github container registry and verify signature
|
# Pull images from github container registry and verify signature
|
||||||
docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
docker pull "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
docker pull "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
docker pull "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
docker pull "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
docker pull "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
validate_image "ghcr.io/home-assistant/i386-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
validate_image "ghcr.io/home-assistant/armhf-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
validate_image "ghcr.io/home-assistant/armv7-homeassistant:${{ needs.init.outputs.version }}"
|
|
||||||
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}"
|
||||||
|
|
||||||
if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then
|
if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then
|
||||||
# Upload images to dockerhub
|
# Upload images to dockerhub
|
||||||
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
push_dockerhub "i386-homeassistant" "${{ needs.init.outputs.version }}"
|
|
||||||
push_dockerhub "armhf-homeassistant" "${{ needs.init.outputs.version }}"
|
|
||||||
push_dockerhub "armv7-homeassistant" "${{ needs.init.outputs.version }}"
|
|
||||||
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
|
push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -456,7 +414,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
@@ -464,7 +422,7 @@ jobs:
|
|||||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
|
|
||||||
@@ -501,7 +459,7 @@ jobs:
|
|||||||
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||||
|
|||||||
46
.github/workflows/ci.yaml
vendored
46
.github/workflows/ci.yaml
vendored
@@ -37,10 +37,10 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 9
|
CACHE_VERSION: 2
|
||||||
UV_CACHE_VERSION: 1
|
UV_CACHE_VERSION: 1
|
||||||
MYPY_CACHE_VERSION: 1
|
MYPY_CACHE_VERSION: 1
|
||||||
HA_SHORT_VERSION: "2025.11"
|
HA_SHORT_VERSION: "2025.12"
|
||||||
DEFAULT_PYTHON: "3.13"
|
DEFAULT_PYTHON: "3.13"
|
||||||
ALL_PYTHON_VERSIONS: "['3.13', '3.14']"
|
ALL_PYTHON_VERSIONS: "['3.13', '3.14']"
|
||||||
# 10.3 is the oldest supported version
|
# 10.3 is the oldest supported version
|
||||||
@@ -99,7 +99,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- &checkout
|
- &checkout
|
||||||
name: Check out code from GitHub
|
name: Check out code from GitHub
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
- name: Generate partial Python venv restore key
|
- name: Generate partial Python venv restore key
|
||||||
id: generate_python_cache_key
|
id: generate_python_cache_key
|
||||||
run: |
|
run: |
|
||||||
@@ -364,13 +364,13 @@ jobs:
|
|||||||
- name: Run check-json
|
- name: Run check-json
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual check-json --all-files
|
pre-commit run --hook-stage manual check-json --all-files --show-diff-on-failure
|
||||||
|
|
||||||
- name: Run prettier (fully)
|
- name: Run prettier (fully)
|
||||||
if: needs.info.outputs.test_full_suite == 'true'
|
if: needs.info.outputs.test_full_suite == 'true'
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual prettier --all-files
|
pre-commit run --hook-stage manual prettier --all-files --show-diff-on-failure
|
||||||
|
|
||||||
- name: Run prettier (partially)
|
- name: Run prettier (partially)
|
||||||
if: needs.info.outputs.test_full_suite == 'false'
|
if: needs.info.outputs.test_full_suite == 'false'
|
||||||
@@ -378,7 +378,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
pre-commit run --hook-stage manual prettier --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*}
|
pre-commit run --hook-stage manual prettier --show-diff-on-failure --files {homeassistant,tests}/components/${{ needs.info.outputs.integrations_glob }}/{*,**/*}
|
||||||
|
|
||||||
- name: Register check executables problem matcher
|
- name: Register check executables problem matcher
|
||||||
run: |
|
run: |
|
||||||
@@ -386,7 +386,7 @@ jobs:
|
|||||||
- name: Run executables check
|
- name: Run executables check
|
||||||
run: |
|
run: |
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files
|
pre-commit run --hook-stage manual check-executables-have-shebangs --all-files --show-diff-on-failure
|
||||||
|
|
||||||
- name: Register codespell problem matcher
|
- name: Register codespell problem matcher
|
||||||
run: |
|
run: |
|
||||||
@@ -428,7 +428,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
|
python-version: &matrix-python ${{ fromJson(needs.info.outputs.python_versions) }}
|
||||||
steps:
|
steps:
|
||||||
- *checkout
|
- *checkout
|
||||||
- &setup-python-matrix
|
- &setup-python-matrix
|
||||||
@@ -502,7 +502,6 @@ jobs:
|
|||||||
libavfilter-dev \
|
libavfilter-dev \
|
||||||
libavformat-dev \
|
libavformat-dev \
|
||||||
libavutil-dev \
|
libavutil-dev \
|
||||||
libgammu-dev \
|
|
||||||
libswresample-dev \
|
libswresample-dev \
|
||||||
libswscale-dev \
|
libswscale-dev \
|
||||||
libudev-dev
|
libudev-dev
|
||||||
@@ -514,9 +513,7 @@ jobs:
|
|||||||
if: steps.cache-apt-check.outputs.cache-hit != 'true'
|
if: steps.cache-apt-check.outputs.cache-hit != 'true'
|
||||||
uses: &actions-cache-save actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: &actions-cache-save actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: *path-apt-cache
|
||||||
${{ env.APT_CACHE_DIR }}
|
|
||||||
${{ env.APT_LIST_CACHE_DIR }}
|
|
||||||
key: *key-apt-cache
|
key: *key-apt-cache
|
||||||
- name: Create Python virtual environment
|
- name: Create Python virtual environment
|
||||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||||
@@ -537,7 +534,7 @@ jobs:
|
|||||||
python --version
|
python --version
|
||||||
uv pip freeze >> pip_freeze.txt
|
uv pip freeze >> pip_freeze.txt
|
||||||
- name: Upload pip_freeze artifact
|
- name: Upload pip_freeze artifact
|
||||||
uses: &actions-upload-artifact actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: &actions-upload-artifact actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: pip-freeze-${{ matrix.python-version }}
|
name: pip-freeze-${{ matrix.python-version }}
|
||||||
path: pip_freeze.txt
|
path: pip_freeze.txt
|
||||||
@@ -625,7 +622,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- *checkout
|
- *checkout
|
||||||
- name: Dependency review
|
- name: Dependency review
|
||||||
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
|
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||||
with:
|
with:
|
||||||
license-check: false # We use our own license audit checks
|
license-check: false # We use our own license audit checks
|
||||||
|
|
||||||
@@ -641,7 +638,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
python-version: *matrix-python
|
||||||
steps:
|
steps:
|
||||||
- *checkout
|
- *checkout
|
||||||
- *setup-python-matrix
|
- *setup-python-matrix
|
||||||
@@ -803,8 +800,7 @@ jobs:
|
|||||||
-o Dir::State::Lists=${{ env.APT_LIST_CACHE_DIR }} \
|
-o Dir::State::Lists=${{ env.APT_LIST_CACHE_DIR }} \
|
||||||
bluez \
|
bluez \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
libturbojpeg \
|
libturbojpeg
|
||||||
libgammu-dev
|
|
||||||
- *checkout
|
- *checkout
|
||||||
- *setup-python-default
|
- *setup-python-default
|
||||||
- *cache-restore-python-default
|
- *cache-restore-python-default
|
||||||
@@ -838,8 +834,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
python-version: *matrix-python
|
||||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
group: &matrix-group ${{ fromJson(needs.info.outputs.test_groups) }}
|
||||||
steps:
|
steps:
|
||||||
- *cache-restore-apt
|
- *cache-restore-apt
|
||||||
- name: Install additional OS dependencies
|
- name: Install additional OS dependencies
|
||||||
@@ -855,7 +851,6 @@ jobs:
|
|||||||
bluez \
|
bluez \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
libturbojpeg \
|
libturbojpeg \
|
||||||
libgammu-dev \
|
|
||||||
libxml2-utils
|
libxml2-utils
|
||||||
- *checkout
|
- *checkout
|
||||||
- *setup-python-matrix
|
- *setup-python-matrix
|
||||||
@@ -869,7 +864,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
|
||||||
- name: Download pytest_buckets
|
- name: Download pytest_buckets
|
||||||
uses: &actions-download-artifact actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: &actions-download-artifact actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: pytest_buckets
|
name: pytest_buckets
|
||||||
- &compile-english-translations
|
- &compile-english-translations
|
||||||
@@ -964,7 +959,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
python-version: *matrix-python
|
||||||
mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }}
|
mariadb-group: ${{ fromJson(needs.info.outputs.mariadb_groups) }}
|
||||||
steps:
|
steps:
|
||||||
- *cache-restore-apt
|
- *cache-restore-apt
|
||||||
@@ -1081,7 +1076,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
python-version: *matrix-python
|
||||||
postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }}
|
postgresql-group: ${{ fromJson(needs.info.outputs.postgresql_groups) }}
|
||||||
steps:
|
steps:
|
||||||
- *cache-restore-apt
|
- *cache-restore-apt
|
||||||
@@ -1218,8 +1213,8 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
|
python-version: *matrix-python
|
||||||
group: ${{ fromJson(needs.info.outputs.test_groups) }}
|
group: *matrix-group
|
||||||
steps:
|
steps:
|
||||||
- *cache-restore-apt
|
- *cache-restore-apt
|
||||||
- name: Install additional OS dependencies
|
- name: Install additional OS dependencies
|
||||||
@@ -1235,7 +1230,6 @@ jobs:
|
|||||||
bluez \
|
bluez \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
libturbojpeg \
|
libturbojpeg \
|
||||||
libgammu-dev \
|
|
||||||
libxml2-utils
|
libxml2-utils
|
||||||
- *checkout
|
- *checkout
|
||||||
- *setup-python-matrix
|
- *setup-python-matrix
|
||||||
|
|||||||
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@@ -21,14 +21,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code from GitHub
|
- name: Check out code from GitHub
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||||
with:
|
with:
|
||||||
languages: python
|
languages: python
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||||
with:
|
with:
|
||||||
category: "/language:python"
|
category: "/language:python"
|
||||||
|
|||||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
|
|||||||
94
.github/workflows/wheels.yml
vendored
94
.github/workflows/wheels.yml
vendored
@@ -31,8 +31,9 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
architectures: ${{ steps.info.outputs.architectures }}
|
architectures: ${{ steps.info.outputs.architectures }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- &checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
name: Checkout the repository
|
||||||
|
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||||
|
|
||||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||||
id: python
|
id: python
|
||||||
@@ -76,37 +77,18 @@ jobs:
|
|||||||
|
|
||||||
# Use C-Extension for SQLAlchemy
|
# Use C-Extension for SQLAlchemy
|
||||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
||||||
|
|
||||||
# Add additional pip wheel build constraints
|
|
||||||
echo "PIP_CONSTRAINT=build_constraints.txt"
|
|
||||||
) > .env_file
|
) > .env_file
|
||||||
|
|
||||||
- name: Write pip wheel build constraints
|
|
||||||
run: |
|
|
||||||
(
|
|
||||||
# ninja 1.11.1.2 + 1.11.1.3 seem to be broken on at least armhf
|
|
||||||
# this caused the numpy builds to fail
|
|
||||||
# https://github.com/scikit-build/ninja-python-distributions/issues/274
|
|
||||||
echo "ninja==1.11.1.1"
|
|
||||||
) > build_constraints.txt
|
|
||||||
|
|
||||||
- name: Upload env_file
|
- name: Upload env_file
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: &actions-upload-artifact actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
path: ./.env_file
|
path: ./.env_file
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
- name: Upload build_constraints
|
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
||||||
with:
|
|
||||||
name: build_constraints
|
|
||||||
path: ./build_constraints.txt
|
|
||||||
overwrite: true
|
|
||||||
|
|
||||||
- name: Upload requirements_diff
|
- name: Upload requirements_diff
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: *actions-upload-artifact
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
path: ./requirements_diff.txt
|
path: ./requirements_diff.txt
|
||||||
@@ -118,7 +100,7 @@ jobs:
|
|||||||
python -m script.gen_requirements_all ci
|
python -m script.gen_requirements_all ci
|
||||||
|
|
||||||
- name: Upload requirements_all_wheels
|
- name: Upload requirements_all_wheels
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: *actions-upload-artifact
|
||||||
with:
|
with:
|
||||||
name: requirements_all_wheels
|
name: requirements_all_wheels
|
||||||
path: ./requirements_all_wheels_*.txt
|
path: ./requirements_all_wheels_*.txt
|
||||||
@@ -127,28 +109,28 @@ jobs:
|
|||||||
name: Build Core wheels ${{ matrix.abi }} for ${{ matrix.arch }} (musllinux_1_2)
|
name: Build Core wheels ${{ matrix.abi }} for ${{ matrix.arch }} (musllinux_1_2)
|
||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
needs: init
|
needs: init
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix: &matrix-build
|
||||||
abi: ["cp313"]
|
abi: ["cp313", "cp314"]
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
- arch: aarch64
|
||||||
|
os: ubuntu-24.04-arm
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- *checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
|
|
||||||
- name: Download env_file
|
- &download-env-file
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
name: Download env_file
|
||||||
|
uses: &actions-download-artifact actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: env_file
|
name: env_file
|
||||||
|
|
||||||
- name: Download build_constraints
|
- &download-requirements-diff
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
name: Download requirements_diff
|
||||||
with:
|
uses: *actions-download-artifact
|
||||||
name: build_constraints
|
|
||||||
|
|
||||||
- name: Download requirements_diff
|
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
|
||||||
with:
|
with:
|
||||||
name: requirements_diff
|
name: requirements_diff
|
||||||
|
|
||||||
@@ -160,7 +142,7 @@ jobs:
|
|||||||
|
|
||||||
# home-assistant/wheels doesn't support sha pinning
|
# home-assistant/wheels doesn't support sha pinning
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2025.09.1
|
uses: &home-assistant-wheels home-assistant/wheels@2025.10.0
|
||||||
with:
|
with:
|
||||||
abi: ${{ matrix.abi }}
|
abi: ${{ matrix.abi }}
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
@@ -177,42 +159,24 @@ jobs:
|
|||||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||||
if: github.repository_owner == 'home-assistant'
|
if: github.repository_owner == 'home-assistant'
|
||||||
needs: init
|
needs: init
|
||||||
runs-on: ubuntu-latest
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix: *matrix-build
|
||||||
abi: ["cp313"]
|
|
||||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- *checkout
|
||||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
||||||
|
|
||||||
- name: Download env_file
|
- *download-env-file
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: env_file
|
|
||||||
|
|
||||||
- name: Download build_constraints
|
- *download-requirements-diff
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: build_constraints
|
|
||||||
|
|
||||||
- name: Download requirements_diff
|
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
|
||||||
with:
|
|
||||||
name: requirements_diff
|
|
||||||
|
|
||||||
- name: Download requirements_all_wheels
|
- name: Download requirements_all_wheels
|
||||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
uses: *actions-download-artifact
|
||||||
with:
|
with:
|
||||||
name: requirements_all_wheels
|
name: requirements_all_wheels
|
||||||
|
|
||||||
- name: Adjust build env
|
- name: Adjust build env
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.arch }}" = "i386" ]; then
|
|
||||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Do not pin numpy in wheels building
|
# Do not pin numpy in wheels building
|
||||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||||
# Don't build wheels for uv as uv requires a greater version of rust as currently available on alpine
|
# Don't build wheels for uv as uv requires a greater version of rust as currently available on alpine
|
||||||
@@ -221,14 +185,14 @@ jobs:
|
|||||||
|
|
||||||
# home-assistant/wheels doesn't support sha pinning
|
# home-assistant/wheels doesn't support sha pinning
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2025.09.1
|
uses: *home-assistant-wheels
|
||||||
with:
|
with:
|
||||||
abi: ${{ matrix.abi }}
|
abi: ${{ matrix.abi }}
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
env-file: true
|
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;zlib-ng-dev"
|
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-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-ng-dev"
|
||||||
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
|
||||||
constraints: "homeassistant/package_constraints.txt"
|
constraints: "homeassistant/package_constraints.txt"
|
||||||
requirements-diff: "requirements_diff.txt"
|
requirements-diff: "requirements_diff.txt"
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -111,6 +111,7 @@ virtualization/vagrant/config
|
|||||||
!.vscode/cSpell.json
|
!.vscode/cSpell.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
|
!.vscode/settings.default.jsonc
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Windows Explorer
|
# Windows Explorer
|
||||||
@@ -140,4 +141,5 @@ pytest_buckets.txt
|
|||||||
|
|
||||||
# AI tooling
|
# AI tooling
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
.serena/
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,13 @@ repos:
|
|||||||
rev: v1.37.1
|
rev: v1.37.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/rbubley/mirrors-prettier
|
||||||
rev: v3.0.3
|
rev: v3.6.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
|
additional_dependencies:
|
||||||
|
- prettier@3.6.2
|
||||||
|
- prettier-plugin-sort-json@4.1.1
|
||||||
- repo: https://github.com/cdce8p/python-typing-update
|
- repo: https://github.com/cdce8p/python-typing-update
|
||||||
rev: v0.6.0
|
rev: v0.6.0
|
||||||
hooks:
|
hooks:
|
||||||
@@ -84,7 +87,7 @@ repos:
|
|||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
language: script
|
language: script
|
||||||
types: [text]
|
types: [text]
|
||||||
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/.+/(quality_scale)\.yaml|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
|
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/.+/(conditions|quality_scale|services|triggers)\.yaml|homeassistant/brands/.*\.json|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
|
||||||
- id: hassfest-metadata
|
- id: hassfest-metadata
|
||||||
name: hassfest-metadata
|
name: hassfest-metadata
|
||||||
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata,docker
|
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata,docker
|
||||||
|
|||||||
24
.prettierrc.js
Normal file
24
.prettierrc.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/** @type {import("prettier").Config} */
|
||||||
|
module.exports = {
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: "./homeassistant/**/*.json",
|
||||||
|
options: {
|
||||||
|
plugins: [require.resolve("prettier-plugin-sort-json")],
|
||||||
|
jsonRecursiveSort: true,
|
||||||
|
jsonSortOrder: JSON.stringify({ [/.*/]: "numeric" }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["manifest.json", "./**/brands/*.json"],
|
||||||
|
options: {
|
||||||
|
// domain and name should stay at the top
|
||||||
|
jsonSortOrder: JSON.stringify({
|
||||||
|
domain: null,
|
||||||
|
name: null,
|
||||||
|
[/.*/]: "numeric",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -107,6 +107,7 @@ homeassistant.components.automation.*
|
|||||||
homeassistant.components.awair.*
|
homeassistant.components.awair.*
|
||||||
homeassistant.components.axis.*
|
homeassistant.components.axis.*
|
||||||
homeassistant.components.azure_storage.*
|
homeassistant.components.azure_storage.*
|
||||||
|
homeassistant.components.backblaze_b2.*
|
||||||
homeassistant.components.backup.*
|
homeassistant.components.backup.*
|
||||||
homeassistant.components.baf.*
|
homeassistant.components.baf.*
|
||||||
homeassistant.components.bang_olufsen.*
|
homeassistant.components.bang_olufsen.*
|
||||||
@@ -182,7 +183,6 @@ homeassistant.components.efergy.*
|
|||||||
homeassistant.components.eheimdigital.*
|
homeassistant.components.eheimdigital.*
|
||||||
homeassistant.components.electrasmart.*
|
homeassistant.components.electrasmart.*
|
||||||
homeassistant.components.electric_kiwi.*
|
homeassistant.components.electric_kiwi.*
|
||||||
homeassistant.components.elevenlabs.*
|
|
||||||
homeassistant.components.elgato.*
|
homeassistant.components.elgato.*
|
||||||
homeassistant.components.elkm1.*
|
homeassistant.components.elkm1.*
|
||||||
homeassistant.components.emulated_hue.*
|
homeassistant.components.emulated_hue.*
|
||||||
@@ -231,6 +231,7 @@ homeassistant.components.google_cloud.*
|
|||||||
homeassistant.components.google_drive.*
|
homeassistant.components.google_drive.*
|
||||||
homeassistant.components.google_photos.*
|
homeassistant.components.google_photos.*
|
||||||
homeassistant.components.google_sheets.*
|
homeassistant.components.google_sheets.*
|
||||||
|
homeassistant.components.google_weather.*
|
||||||
homeassistant.components.govee_ble.*
|
homeassistant.components.govee_ble.*
|
||||||
homeassistant.components.gpsd.*
|
homeassistant.components.gpsd.*
|
||||||
homeassistant.components.greeneye_monitor.*
|
homeassistant.components.greeneye_monitor.*
|
||||||
@@ -279,6 +280,7 @@ homeassistant.components.imap.*
|
|||||||
homeassistant.components.imgw_pib.*
|
homeassistant.components.imgw_pib.*
|
||||||
homeassistant.components.immich.*
|
homeassistant.components.immich.*
|
||||||
homeassistant.components.incomfort.*
|
homeassistant.components.incomfort.*
|
||||||
|
homeassistant.components.inels.*
|
||||||
homeassistant.components.input_button.*
|
homeassistant.components.input_button.*
|
||||||
homeassistant.components.input_select.*
|
homeassistant.components.input_select.*
|
||||||
homeassistant.components.input_text.*
|
homeassistant.components.input_text.*
|
||||||
@@ -395,7 +397,6 @@ homeassistant.components.otbr.*
|
|||||||
homeassistant.components.overkiz.*
|
homeassistant.components.overkiz.*
|
||||||
homeassistant.components.overseerr.*
|
homeassistant.components.overseerr.*
|
||||||
homeassistant.components.p1_monitor.*
|
homeassistant.components.p1_monitor.*
|
||||||
homeassistant.components.pandora.*
|
|
||||||
homeassistant.components.panel_custom.*
|
homeassistant.components.panel_custom.*
|
||||||
homeassistant.components.paperless_ngx.*
|
homeassistant.components.paperless_ngx.*
|
||||||
homeassistant.components.peblar.*
|
homeassistant.components.peblar.*
|
||||||
@@ -478,6 +479,7 @@ homeassistant.components.skybell.*
|
|||||||
homeassistant.components.slack.*
|
homeassistant.components.slack.*
|
||||||
homeassistant.components.sleep_as_android.*
|
homeassistant.components.sleep_as_android.*
|
||||||
homeassistant.components.sleepiq.*
|
homeassistant.components.sleepiq.*
|
||||||
|
homeassistant.components.sma.*
|
||||||
homeassistant.components.smhi.*
|
homeassistant.components.smhi.*
|
||||||
homeassistant.components.smlight.*
|
homeassistant.components.smlight.*
|
||||||
homeassistant.components.smtp.*
|
homeassistant.components.smtp.*
|
||||||
@@ -577,6 +579,7 @@ homeassistant.components.wiz.*
|
|||||||
homeassistant.components.wled.*
|
homeassistant.components.wled.*
|
||||||
homeassistant.components.workday.*
|
homeassistant.components.workday.*
|
||||||
homeassistant.components.worldclock.*
|
homeassistant.components.worldclock.*
|
||||||
|
homeassistant.components.xbox.*
|
||||||
homeassistant.components.xiaomi_ble.*
|
homeassistant.components.xiaomi_ble.*
|
||||||
homeassistant.components.yale_smart_alarm.*
|
homeassistant.components.yale_smart_alarm.*
|
||||||
homeassistant.components.yalexs_ble.*
|
homeassistant.components.yalexs_ble.*
|
||||||
|
|||||||
@@ -7,13 +7,19 @@
|
|||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
// https://code.visualstudio.com/docs/python/linting#_general-settings
|
// https://code.visualstudio.com/docs/python/linting#_general-settings
|
||||||
"pylint.importStrategy": "fromEnvironment",
|
"pylint.importStrategy": "fromEnvironment",
|
||||||
|
// Pyright is too pedantic for Home Assistant
|
||||||
|
"python.analysis.typeCheckingMode": "basic",
|
||||||
|
"[python]": {
|
||||||
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
|
},
|
||||||
|
"[json][jsonc][yaml]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
},
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": [
|
"fileMatch": ["homeassistant/components/*/manifest.json"],
|
||||||
"homeassistant/components/*/manifest.json"
|
// This value differs between working with devcontainer and locally, therefore this value should NOT be in sync!
|
||||||
],
|
"url": "./script/json_schemas/manifest_schema.json",
|
||||||
// This value differs between working with devcontainer and locally, therefor this value should NOT be in sync!
|
},
|
||||||
"url": "./script/json_schemas/manifest_schema.json"
|
],
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
38
CODEOWNERS
generated
38
CODEOWNERS
generated
@@ -69,6 +69,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/airly/ @bieniu
|
/tests/components/airly/ @bieniu
|
||||||
/homeassistant/components/airnow/ @asymworks
|
/homeassistant/components/airnow/ @asymworks
|
||||||
/tests/components/airnow/ @asymworks
|
/tests/components/airnow/ @asymworks
|
||||||
|
/homeassistant/components/airobot/ @mettolen
|
||||||
|
/tests/components/airobot/ @mettolen
|
||||||
/homeassistant/components/airos/ @CoMPaTech
|
/homeassistant/components/airos/ @CoMPaTech
|
||||||
/tests/components/airos/ @CoMPaTech
|
/tests/components/airos/ @CoMPaTech
|
||||||
/homeassistant/components/airq/ @Sibgatulin @dl2080
|
/homeassistant/components/airq/ @Sibgatulin @dl2080
|
||||||
@@ -196,6 +198,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/azure_service_bus/ @hfurubotten
|
/homeassistant/components/azure_service_bus/ @hfurubotten
|
||||||
/homeassistant/components/azure_storage/ @zweckj
|
/homeassistant/components/azure_storage/ @zweckj
|
||||||
/tests/components/azure_storage/ @zweckj
|
/tests/components/azure_storage/ @zweckj
|
||||||
|
/homeassistant/components/backblaze_b2/ @hugo-vrijswijk @ElCruncharino
|
||||||
|
/tests/components/backblaze_b2/ @hugo-vrijswijk @ElCruncharino
|
||||||
/homeassistant/components/backup/ @home-assistant/core
|
/homeassistant/components/backup/ @home-assistant/core
|
||||||
/tests/components/backup/ @home-assistant/core
|
/tests/components/backup/ @home-assistant/core
|
||||||
/homeassistant/components/baf/ @bdraco @jfroy
|
/homeassistant/components/baf/ @bdraco @jfroy
|
||||||
@@ -316,8 +320,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/cpuspeed/ @fabaff
|
/tests/components/cpuspeed/ @fabaff
|
||||||
/homeassistant/components/crownstone/ @Crownstone @RicArch97
|
/homeassistant/components/crownstone/ @Crownstone @RicArch97
|
||||||
/tests/components/crownstone/ @Crownstone @RicArch97
|
/tests/components/crownstone/ @Crownstone @RicArch97
|
||||||
/homeassistant/components/cups/ @fabaff
|
|
||||||
/tests/components/cups/ @fabaff
|
|
||||||
/homeassistant/components/cync/ @Kinachi249
|
/homeassistant/components/cync/ @Kinachi249
|
||||||
/tests/components/cync/ @Kinachi249
|
/tests/components/cync/ @Kinachi249
|
||||||
/homeassistant/components/daikin/ @fredrike
|
/homeassistant/components/daikin/ @fredrike
|
||||||
@@ -494,6 +496,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/filesize/ @gjohansson-ST
|
/tests/components/filesize/ @gjohansson-ST
|
||||||
/homeassistant/components/filter/ @dgomes
|
/homeassistant/components/filter/ @dgomes
|
||||||
/tests/components/filter/ @dgomes
|
/tests/components/filter/ @dgomes
|
||||||
|
/homeassistant/components/fing/ @Lorenzo-Gasparini
|
||||||
|
/tests/components/fing/ @Lorenzo-Gasparini
|
||||||
/homeassistant/components/firefly_iii/ @erwindouna
|
/homeassistant/components/firefly_iii/ @erwindouna
|
||||||
/tests/components/firefly_iii/ @erwindouna
|
/tests/components/firefly_iii/ @erwindouna
|
||||||
/homeassistant/components/fireservicerota/ @cyberjunky
|
/homeassistant/components/fireservicerota/ @cyberjunky
|
||||||
@@ -508,8 +512,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/fjaraskupan/ @elupus
|
/tests/components/fjaraskupan/ @elupus
|
||||||
/homeassistant/components/flexit_bacnet/ @lellky @piotrbulinski
|
/homeassistant/components/flexit_bacnet/ @lellky @piotrbulinski
|
||||||
/tests/components/flexit_bacnet/ @lellky @piotrbulinski
|
/tests/components/flexit_bacnet/ @lellky @piotrbulinski
|
||||||
/homeassistant/components/flick_electric/ @ZephireNZ
|
|
||||||
/tests/components/flick_electric/ @ZephireNZ
|
|
||||||
/homeassistant/components/flipr/ @cnico
|
/homeassistant/components/flipr/ @cnico
|
||||||
/tests/components/flipr/ @cnico
|
/tests/components/flipr/ @cnico
|
||||||
/homeassistant/components/flo/ @dmulcahey
|
/homeassistant/components/flo/ @dmulcahey
|
||||||
@@ -607,6 +609,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/google_tasks/ @allenporter
|
/tests/components/google_tasks/ @allenporter
|
||||||
/homeassistant/components/google_travel_time/ @eifinger
|
/homeassistant/components/google_travel_time/ @eifinger
|
||||||
/tests/components/google_travel_time/ @eifinger
|
/tests/components/google_travel_time/ @eifinger
|
||||||
|
/homeassistant/components/google_weather/ @tronikos
|
||||||
|
/tests/components/google_weather/ @tronikos
|
||||||
/homeassistant/components/govee_ble/ @bdraco
|
/homeassistant/components/govee_ble/ @bdraco
|
||||||
/tests/components/govee_ble/ @bdraco
|
/tests/components/govee_ble/ @bdraco
|
||||||
/homeassistant/components/govee_light_local/ @Galorhallen
|
/homeassistant/components/govee_light_local/ @Galorhallen
|
||||||
@@ -625,6 +629,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/guardian/ @bachya
|
/tests/components/guardian/ @bachya
|
||||||
/homeassistant/components/habitica/ @tr4nt0r
|
/homeassistant/components/habitica/ @tr4nt0r
|
||||||
/tests/components/habitica/ @tr4nt0r
|
/tests/components/habitica/ @tr4nt0r
|
||||||
|
/homeassistant/components/hanna/ @bestycame
|
||||||
|
/tests/components/hanna/ @bestycame
|
||||||
/homeassistant/components/hardkernel/ @home-assistant/core
|
/homeassistant/components/hardkernel/ @home-assistant/core
|
||||||
/tests/components/hardkernel/ @home-assistant/core
|
/tests/components/hardkernel/ @home-assistant/core
|
||||||
/homeassistant/components/hardware/ @home-assistant/core
|
/homeassistant/components/hardware/ @home-assistant/core
|
||||||
@@ -741,6 +747,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/improv_ble/ @emontnemery
|
/tests/components/improv_ble/ @emontnemery
|
||||||
/homeassistant/components/incomfort/ @jbouwh
|
/homeassistant/components/incomfort/ @jbouwh
|
||||||
/tests/components/incomfort/ @jbouwh
|
/tests/components/incomfort/ @jbouwh
|
||||||
|
/homeassistant/components/inels/ @epdevlab
|
||||||
|
/tests/components/inels/ @epdevlab
|
||||||
/homeassistant/components/influxdb/ @mdegat01
|
/homeassistant/components/influxdb/ @mdegat01
|
||||||
/tests/components/influxdb/ @mdegat01
|
/tests/components/influxdb/ @mdegat01
|
||||||
/homeassistant/components/inkbird/ @bdraco
|
/homeassistant/components/inkbird/ @bdraco
|
||||||
@@ -842,6 +850,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/kraken/ @eifinger
|
/tests/components/kraken/ @eifinger
|
||||||
/homeassistant/components/kulersky/ @emlove
|
/homeassistant/components/kulersky/ @emlove
|
||||||
/tests/components/kulersky/ @emlove
|
/tests/components/kulersky/ @emlove
|
||||||
|
/homeassistant/components/labs/ @home-assistant/core
|
||||||
|
/tests/components/labs/ @home-assistant/core
|
||||||
/homeassistant/components/lacrosse_view/ @IceBotYT
|
/homeassistant/components/lacrosse_view/ @IceBotYT
|
||||||
/tests/components/lacrosse_view/ @IceBotYT
|
/tests/components/lacrosse_view/ @IceBotYT
|
||||||
/homeassistant/components/lamarzocco/ @zweckj
|
/homeassistant/components/lamarzocco/ @zweckj
|
||||||
@@ -1015,8 +1025,8 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/msteams/ @peroyvind
|
/homeassistant/components/msteams/ @peroyvind
|
||||||
/homeassistant/components/mullvad/ @meichthys
|
/homeassistant/components/mullvad/ @meichthys
|
||||||
/tests/components/mullvad/ @meichthys
|
/tests/components/mullvad/ @meichthys
|
||||||
/homeassistant/components/music_assistant/ @music-assistant
|
/homeassistant/components/music_assistant/ @music-assistant @arturpragacz
|
||||||
/tests/components/music_assistant/ @music-assistant
|
/tests/components/music_assistant/ @music-assistant @arturpragacz
|
||||||
/homeassistant/components/mutesync/ @currentoor
|
/homeassistant/components/mutesync/ @currentoor
|
||||||
/tests/components/mutesync/ @currentoor
|
/tests/components/mutesync/ @currentoor
|
||||||
/homeassistant/components/my/ @home-assistant/core
|
/homeassistant/components/my/ @home-assistant/core
|
||||||
@@ -1372,6 +1382,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/sanix/ @tomaszsluszniak
|
/tests/components/sanix/ @tomaszsluszniak
|
||||||
/homeassistant/components/satel_integra/ @Tommatheussen
|
/homeassistant/components/satel_integra/ @Tommatheussen
|
||||||
/tests/components/satel_integra/ @Tommatheussen
|
/tests/components/satel_integra/ @Tommatheussen
|
||||||
|
/homeassistant/components/saunum/ @mettolen
|
||||||
|
/tests/components/saunum/ @mettolen
|
||||||
/homeassistant/components/scene/ @home-assistant/core
|
/homeassistant/components/scene/ @home-assistant/core
|
||||||
/tests/components/scene/ @home-assistant/core
|
/tests/components/scene/ @home-assistant/core
|
||||||
/homeassistant/components/schedule/ @home-assistant/core
|
/homeassistant/components/schedule/ @home-assistant/core
|
||||||
@@ -1475,8 +1487,6 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/smhi/ @gjohansson-ST
|
/tests/components/smhi/ @gjohansson-ST
|
||||||
/homeassistant/components/smlight/ @tl-sl
|
/homeassistant/components/smlight/ @tl-sl
|
||||||
/tests/components/smlight/ @tl-sl
|
/tests/components/smlight/ @tl-sl
|
||||||
/homeassistant/components/sms/ @ocalvo
|
|
||||||
/tests/components/sms/ @ocalvo
|
|
||||||
/homeassistant/components/snapcast/ @luar123
|
/homeassistant/components/snapcast/ @luar123
|
||||||
/tests/components/snapcast/ @luar123
|
/tests/components/snapcast/ @luar123
|
||||||
/homeassistant/components/snmp/ @nmaggioni
|
/homeassistant/components/snmp/ @nmaggioni
|
||||||
@@ -1539,6 +1549,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/suez_water/ @ooii @jb101010-2
|
/tests/components/suez_water/ @ooii @jb101010-2
|
||||||
/homeassistant/components/sun/ @home-assistant/core
|
/homeassistant/components/sun/ @home-assistant/core
|
||||||
/tests/components/sun/ @home-assistant/core
|
/tests/components/sun/ @home-assistant/core
|
||||||
|
/homeassistant/components/sunricher_dali/ @niracler
|
||||||
|
/tests/components/sunricher_dali/ @niracler
|
||||||
/homeassistant/components/supla/ @mwegrzynek
|
/homeassistant/components/supla/ @mwegrzynek
|
||||||
/homeassistant/components/surepetcare/ @benleb @danielhiversen
|
/homeassistant/components/surepetcare/ @benleb @danielhiversen
|
||||||
/tests/components/surepetcare/ @benleb @danielhiversen
|
/tests/components/surepetcare/ @benleb @danielhiversen
|
||||||
@@ -1715,8 +1727,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/vallox/ @andre-richter @slovdahl @viiru- @yozik04
|
/tests/components/vallox/ @andre-richter @slovdahl @viiru- @yozik04
|
||||||
/homeassistant/components/valve/ @home-assistant/core
|
/homeassistant/components/valve/ @home-assistant/core
|
||||||
/tests/components/valve/ @home-assistant/core
|
/tests/components/valve/ @home-assistant/core
|
||||||
/homeassistant/components/vegehub/ @ghowevege
|
/homeassistant/components/vegehub/ @thulrus
|
||||||
/tests/components/vegehub/ @ghowevege
|
/tests/components/vegehub/ @thulrus
|
||||||
/homeassistant/components/velbus/ @Cereal2nd @brefra
|
/homeassistant/components/velbus/ @Cereal2nd @brefra
|
||||||
/tests/components/velbus/ @Cereal2nd @brefra
|
/tests/components/velbus/ @Cereal2nd @brefra
|
||||||
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio @wollew
|
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio @wollew
|
||||||
@@ -1730,6 +1742,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak @sapuseven
|
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak @sapuseven
|
||||||
/homeassistant/components/vicare/ @CFenner
|
/homeassistant/components/vicare/ @CFenner
|
||||||
/tests/components/vicare/ @CFenner
|
/tests/components/vicare/ @CFenner
|
||||||
|
/homeassistant/components/victron_ble/ @rajlaud
|
||||||
|
/tests/components/victron_ble/ @rajlaud
|
||||||
/homeassistant/components/victron_remote_monitoring/ @AndyTempel
|
/homeassistant/components/victron_remote_monitoring/ @AndyTempel
|
||||||
/tests/components/victron_remote_monitoring/ @AndyTempel
|
/tests/components/victron_remote_monitoring/ @AndyTempel
|
||||||
/homeassistant/components/vilfo/ @ManneW
|
/homeassistant/components/vilfo/ @ManneW
|
||||||
@@ -1815,8 +1829,8 @@ build.json @home-assistant/supervisor
|
|||||||
/tests/components/ws66i/ @ssaenger
|
/tests/components/ws66i/ @ssaenger
|
||||||
/homeassistant/components/wyoming/ @synesthesiam
|
/homeassistant/components/wyoming/ @synesthesiam
|
||||||
/tests/components/wyoming/ @synesthesiam
|
/tests/components/wyoming/ @synesthesiam
|
||||||
/homeassistant/components/xbox/ @hunterjm
|
/homeassistant/components/xbox/ @hunterjm @tr4nt0r
|
||||||
/tests/components/xbox/ @hunterjm
|
/tests/components/xbox/ @hunterjm @tr4nt0r
|
||||||
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
|
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||||
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
|
/tests/components/xiaomi_aqara/ @danielhiversen @syssi
|
||||||
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79
|
/homeassistant/components/xiaomi_ble/ @Jc2k @Ernst79
|
||||||
|
|||||||
6
Dockerfile
generated
6
Dockerfile
generated
@@ -21,17 +21,15 @@ ARG BUILD_ARCH
|
|||||||
RUN \
|
RUN \
|
||||||
case "${BUILD_ARCH}" in \
|
case "${BUILD_ARCH}" in \
|
||||||
"aarch64") go2rtc_suffix='arm64' ;; \
|
"aarch64") go2rtc_suffix='arm64' ;; \
|
||||||
"armhf") go2rtc_suffix='armv6' ;; \
|
|
||||||
"armv7") go2rtc_suffix='arm' ;; \
|
|
||||||
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
||||||
esac \
|
esac \
|
||||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.9/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.12/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||||
&& chmod +x /bin/go2rtc \
|
&& chmod +x /bin/go2rtc \
|
||||||
# Verify go2rtc can be executed
|
# Verify go2rtc can be executed
|
||||||
&& go2rtc --version
|
&& go2rtc --version
|
||||||
|
|
||||||
# Install uv
|
# Install uv
|
||||||
RUN pip3 install uv==0.8.9
|
RUN pip3 install uv==0.9.6
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ RUN \
|
|||||||
libavcodec-dev \
|
libavcodec-dev \
|
||||||
libavdevice-dev \
|
libavdevice-dev \
|
||||||
libavutil-dev \
|
libavutil-dev \
|
||||||
libgammu-dev \
|
|
||||||
libswscale-dev \
|
libswscale-dev \
|
||||||
libswresample-dev \
|
libswresample-dev \
|
||||||
libavfilter-dev \
|
libavfilter-dev \
|
||||||
|
|||||||
10
build.yaml
10
build.yaml
@@ -1,13 +1,7 @@
|
|||||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||||
build_from:
|
build_from:
|
||||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.10.1
|
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.11.0
|
||||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.10.1
|
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.11.0
|
||||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.10.1
|
|
||||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.10.1
|
|
||||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.10.1
|
|
||||||
codenotary:
|
|
||||||
signer: notary@home-assistant.io
|
|
||||||
base_image: notary@home-assistant.io
|
|
||||||
cosign:
|
cosign:
|
||||||
base_identity: https://github.com/home-assistant/docker/.*
|
base_identity: https://github.com/home-assistant/docker/.*
|
||||||
identity: https://github.com/home-assistant/core/.*
|
identity: https://github.com/home-assistant/core/.*
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ Sending HOTP through notify service
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
@@ -304,14 +303,15 @@ class NotifySetupFlow(SetupFlow[NotifyAuthModule]):
|
|||||||
if not self._available_notify_services:
|
if not self._available_notify_services:
|
||||||
return self.async_abort(reason="no_available_service")
|
return self.async_abort(reason="no_available_service")
|
||||||
|
|
||||||
schema: dict[str, Any] = OrderedDict()
|
schema = vol.Schema(
|
||||||
schema["notify_service"] = vol.In(self._available_notify_services)
|
{
|
||||||
schema["target"] = vol.Optional(str)
|
vol.Required("notify_service"): vol.In(self._available_notify_services),
|
||||||
|
vol.Optional("target"): str,
|
||||||
return self.async_show_form(
|
}
|
||||||
step_id="init", data_schema=vol.Schema(schema), errors=errors
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="init", data_schema=schema, errors=errors)
|
||||||
|
|
||||||
async def async_step_setup(
|
async def async_step_setup(
|
||||||
self, user_input: dict[str, str] | None = None
|
self, user_input: dict[str, str] | None = None
|
||||||
) -> FlowResult:
|
) -> FlowResult:
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ INPUT_FIELD_CODE = "code"
|
|||||||
|
|
||||||
DUMMY_SECRET = "FPPTH34D4E3MI2HG"
|
DUMMY_SECRET = "FPPTH34D4E3MI2HG"
|
||||||
|
|
||||||
|
GOOGLE_AUTHENTICATOR_URL = "https://support.google.com/accounts/answer/1066447"
|
||||||
|
AUTHY_URL = "https://authy.com/"
|
||||||
|
|
||||||
|
|
||||||
def _generate_qr_code(data: str) -> str:
|
def _generate_qr_code(data: str) -> str:
|
||||||
"""Generate a base64 PNG string represent QR Code image of data."""
|
"""Generate a base64 PNG string represent QR Code image of data."""
|
||||||
@@ -229,6 +232,8 @@ class TotpSetupFlow(SetupFlow[TotpAuthModule]):
|
|||||||
"code": self._ota_secret,
|
"code": self._ota_secret,
|
||||||
"url": self._url,
|
"url": self._url,
|
||||||
"qr_code": self._image,
|
"qr_code": self._image,
|
||||||
|
"google_authenticator_url": GOOGLE_AUTHENTICATOR_URL,
|
||||||
|
"authy_url": AUTHY_URL,
|
||||||
},
|
},
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -179,12 +179,18 @@ class Data:
|
|||||||
user_hash = base64.b64decode(found["password"])
|
user_hash = base64.b64decode(found["password"])
|
||||||
|
|
||||||
# bcrypt.checkpw is timing-safe
|
# bcrypt.checkpw is timing-safe
|
||||||
if not bcrypt.checkpw(password.encode(), user_hash):
|
# With bcrypt 5.0 passing a password longer than 72 bytes raises a ValueError.
|
||||||
|
# Previously the password was silently truncated.
|
||||||
|
# https://github.com/pyca/bcrypt/pull/1000
|
||||||
|
if not bcrypt.checkpw(password.encode()[:72], user_hash):
|
||||||
raise InvalidAuth
|
raise InvalidAuth
|
||||||
|
|
||||||
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
|
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
|
||||||
"""Encode a password."""
|
"""Encode a password."""
|
||||||
hashed: bytes = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
|
# With bcrypt 5.0 passing a password longer than 72 bytes raises a ValueError.
|
||||||
|
# Previously the password was silently truncated.
|
||||||
|
# https://github.com/pyca/bcrypt/pull/1000
|
||||||
|
hashed: bytes = bcrypt.hashpw(password.encode()[:72], bcrypt.gensalt(rounds=12))
|
||||||
|
|
||||||
if for_storage:
|
if for_storage:
|
||||||
hashed = base64.b64encode(hashed)
|
hashed = base64.b64encode(hashed)
|
||||||
|
|||||||
@@ -176,6 +176,8 @@ FRONTEND_INTEGRATIONS = {
|
|||||||
STAGE_0_INTEGRATIONS = (
|
STAGE_0_INTEGRATIONS = (
|
||||||
# Load logging and http deps as soon as possible
|
# Load logging and http deps as soon as possible
|
||||||
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS, None),
|
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS, None),
|
||||||
|
# Setup labs for preview features
|
||||||
|
("labs", {"labs"}, STAGE_0_SUBSTAGE_TIMEOUT),
|
||||||
# Setup frontend
|
# Setup frontend
|
||||||
("frontend", FRONTEND_INTEGRATIONS, None),
|
("frontend", FRONTEND_INTEGRATIONS, None),
|
||||||
# Setup recorder
|
# Setup recorder
|
||||||
@@ -212,6 +214,7 @@ DEFAULT_INTEGRATIONS = {
|
|||||||
"backup",
|
"backup",
|
||||||
"frontend",
|
"frontend",
|
||||||
"hardware",
|
"hardware",
|
||||||
|
"labs",
|
||||||
"logger",
|
"logger",
|
||||||
"network",
|
"network",
|
||||||
"system_health",
|
"system_health",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"google_tasks",
|
"google_tasks",
|
||||||
"google_translate",
|
"google_translate",
|
||||||
"google_travel_time",
|
"google_travel_time",
|
||||||
|
"google_weather",
|
||||||
"google_wifi",
|
"google_wifi",
|
||||||
"google",
|
"google",
|
||||||
"nest",
|
"nest",
|
||||||
|
|||||||
5
homeassistant/brands/victron.json
Normal file
5
homeassistant/brands/victron.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "victron",
|
||||||
|
"name": "Victron",
|
||||||
|
"integrations": ["victron_ble", "victron_remote_monitoring"]
|
||||||
|
}
|
||||||
@@ -1,11 +1,5 @@
|
|||||||
{
|
{
|
||||||
"domain": "yale",
|
"domain": "yale",
|
||||||
"name": "Yale",
|
"name": "Yale (non-US/Canada)",
|
||||||
"integrations": [
|
"integrations": ["yale", "yalexs_ble", "yale_smart_alarm"]
|
||||||
"august",
|
|
||||||
"yale_smart_alarm",
|
|
||||||
"yalexs_ble",
|
|
||||||
"yale_home",
|
|
||||||
"yale"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
homeassistant/brands/yale_august.json
Normal file
5
homeassistant/brands/yale_august.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"domain": "yale_august",
|
||||||
|
"name": "Yale August (US/Canada)",
|
||||||
|
"integrations": ["august", "august_ble"]
|
||||||
|
}
|
||||||
@@ -1,70 +1,70 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"title": "Fill in your Abode login information",
|
|
||||||
"data": {
|
|
||||||
"username": "[%key:common::config_flow::data::email%]",
|
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mfa": {
|
|
||||||
"title": "Enter your MFA code for Abode",
|
|
||||||
"data": {
|
|
||||||
"mfa_code": "MFA code (6-digits)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"reauth_confirm": {
|
|
||||||
"title": "[%key:component::abode::config::step::user::title%]",
|
|
||||||
"data": {
|
|
||||||
"username": "[%key:common::config_flow::data::email%]",
|
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"invalid_mfa_code": "Invalid MFA code"
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"invalid_mfa_code": "Invalid MFA code"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"mfa": {
|
||||||
|
"data": {
|
||||||
|
"mfa_code": "MFA code (6-digits)"
|
||||||
|
},
|
||||||
|
"title": "Enter your MFA code for Abode"
|
||||||
|
},
|
||||||
|
"reauth_confirm": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"username": "[%key:common::config_flow::data::email%]"
|
||||||
|
},
|
||||||
|
"title": "[%key:component::abode::config::step::user::title%]"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"username": "[%key:common::config_flow::data::email%]"
|
||||||
|
},
|
||||||
|
"title": "Fill in your Abode login information"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"capture_image": {
|
"capture_image": {
|
||||||
"name": "Capture image",
|
|
||||||
"description": "Requests a new image capture from a camera device.",
|
"description": "Requests a new image capture from a camera device.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"name": "Entity",
|
"description": "Entity ID of the camera to request an image from.",
|
||||||
"description": "Entity ID of the camera to request an image from."
|
"name": "Entity"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Capture image"
|
||||||
},
|
},
|
||||||
"change_setting": {
|
"change_setting": {
|
||||||
"name": "Change setting",
|
|
||||||
"description": "Changes an Abode system setting.",
|
"description": "Changes an Abode system setting.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"setting": {
|
"setting": {
|
||||||
"name": "Setting",
|
"description": "Setting to change.",
|
||||||
"description": "Setting to change."
|
"name": "Setting"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"name": "Value",
|
"description": "Value of the setting.",
|
||||||
"description": "Value of the setting."
|
"name": "Value"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Change setting"
|
||||||
},
|
},
|
||||||
"trigger_automation": {
|
"trigger_automation": {
|
||||||
"name": "Trigger automation",
|
|
||||||
"description": "Triggers an Abode automation.",
|
"description": "Triggers an Abode automation.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"name": "Entity",
|
"description": "Entity ID of the automation to trigger.",
|
||||||
"description": "Entity ID of the automation to trigger."
|
"name": "Entity"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Trigger automation"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
"timer_running": {
|
"timer_running": {
|
||||||
"default": "mdi:timer",
|
"default": "mdi:timer",
|
||||||
"state": {
|
"state": {
|
||||||
"on": "mdi:timer-play",
|
"off": "mdi:timer-off",
|
||||||
"off": "mdi:timer-off"
|
"on": "mdi:timer-play"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"tare": {
|
|
||||||
"default": "mdi:scale-balance"
|
|
||||||
},
|
|
||||||
"reset_timer": {
|
"reset_timer": {
|
||||||
"default": "mdi:timer-refresh"
|
"default": "mdi:timer-refresh"
|
||||||
},
|
},
|
||||||
"start_stop": {
|
"start_stop": {
|
||||||
"default": "mdi:timer-play"
|
"default": "mdi:timer-play"
|
||||||
|
},
|
||||||
|
"tare": {
|
||||||
|
"default": "mdi:scale-balance"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"flow_title": "{name}",
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||||
@@ -10,18 +9,19 @@
|
|||||||
"device_not_found": "Device could not be found.",
|
"device_not_found": "Device could not be found.",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
|
"flow_title": "{name}",
|
||||||
"step": {
|
"step": {
|
||||||
"bluetooth_confirm": {
|
"bluetooth_confirm": {
|
||||||
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
|
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "[%key:component::bluetooth::config::step::user::description%]",
|
|
||||||
"data": {
|
"data": {
|
||||||
"address": "[%key:common::config_flow::data::device%]"
|
"address": "[%key:common::config_flow::data::device%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"address": "Select Acaia scale you want to set up"
|
"address": "Select Acaia scale you want to set up"
|
||||||
}
|
},
|
||||||
|
"description": "[%key:component::bluetooth::config::step::user::description%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -32,14 +32,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"tare": {
|
|
||||||
"name": "Tare"
|
|
||||||
},
|
|
||||||
"reset_timer": {
|
"reset_timer": {
|
||||||
"name": "Reset timer"
|
"name": "Reset timer"
|
||||||
},
|
},
|
||||||
"start_stop": {
|
"start_stop": {
|
||||||
"name": "Start/stop timer"
|
"name": "Start/stop timer"
|
||||||
|
},
|
||||||
|
"tare": {
|
||||||
|
"name": "Tare"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,8 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||||
"data": {
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
|
||||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
|
||||||
"longitude": "[%key:common::config_flow::data::longitude%]"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"api_key": "API key generated in the AccuWeather APIs portal."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"reauth_confirm": {
|
|
||||||
"data": {
|
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"api_key": "[%key:component::accuweather::config::step::user::data_description::api_key%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "Some sensors are not enabled by default. You can enable them in the entity registry after the integration configuration."
|
"default": "Some sensors are not enabled by default. You can enable them in the entity registry after the integration configuration."
|
||||||
@@ -29,9 +12,26 @@
|
|||||||
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
||||||
"requests_exceeded": "The allowed number of requests to the AccuWeather API has been exceeded. You have to wait or change the API key."
|
"requests_exceeded": "The allowed number of requests to the AccuWeather API has been exceeded. You have to wait or change the API key."
|
||||||
},
|
},
|
||||||
"abort": {
|
"step": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
"reauth_confirm": {
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
"data": {
|
||||||
|
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"api_key": "[%key:component::accuweather::config::step::user::data_description::api_key%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
|
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||||
|
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||||
|
"name": "[%key:common::config_flow::data::name%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"api_key": "API key generated in the AccuWeather APIs portal."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -120,9 +120,9 @@
|
|||||||
"pressure_tendency": {
|
"pressure_tendency": {
|
||||||
"name": "Pressure tendency",
|
"name": "Pressure tendency",
|
||||||
"state": {
|
"state": {
|
||||||
"steady": "Steady",
|
"falling": "Falling",
|
||||||
"rising": "Rising",
|
"rising": "Rising",
|
||||||
"falling": "Falling"
|
"steady": "Steady"
|
||||||
},
|
},
|
||||||
"state_attributes": {
|
"state_attributes": {
|
||||||
"options": {
|
"options": {
|
||||||
@@ -227,9 +227,6 @@
|
|||||||
"wet_bulb_temperature": {
|
"wet_bulb_temperature": {
|
||||||
"name": "Wet bulb temperature"
|
"name": "Wet bulb temperature"
|
||||||
},
|
},
|
||||||
"wind_speed": {
|
|
||||||
"name": "[%key:component::weather::entity_component::_::state_attributes::wind_speed::name%]"
|
|
||||||
},
|
|
||||||
"wind_chill_temperature": {
|
"wind_chill_temperature": {
|
||||||
"name": "Wind chill temperature"
|
"name": "Wind chill temperature"
|
||||||
},
|
},
|
||||||
@@ -242,6 +239,9 @@
|
|||||||
"wind_gust_speed_night": {
|
"wind_gust_speed_night": {
|
||||||
"name": "Wind gust speed night {forecast_day}"
|
"name": "Wind gust speed night {forecast_day}"
|
||||||
},
|
},
|
||||||
|
"wind_speed": {
|
||||||
|
"name": "[%key:component::weather::entity_component::_::state_attributes::wind_speed::name%]"
|
||||||
|
},
|
||||||
"wind_speed_day": {
|
"wind_speed_day": {
|
||||||
"name": "Wind speed day {forecast_day}"
|
"name": "Wind speed day {forecast_day}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"title": "Pick a hub to add",
|
|
||||||
"data": {
|
|
||||||
"id": "Host ID"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"id": "Host ID"
|
||||||
|
},
|
||||||
|
"title": "Pick a hub to add"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""The Actron Air integration."""
|
"""The Actron Air integration."""
|
||||||
|
|
||||||
from actron_neo_api import (
|
from actron_neo_api import (
|
||||||
ActronAirNeoACSystem,
|
ActronAirACSystem,
|
||||||
ActronNeoAPI,
|
ActronAirAPI,
|
||||||
ActronNeoAPIError,
|
ActronAirAPIError,
|
||||||
ActronNeoAuthError,
|
ActronAirAuthError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.const import CONF_API_TOKEN, Platform
|
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||||
@@ -23,16 +23,16 @@ PLATFORM = [Platform.CLIMATE]
|
|||||||
async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) -> bool:
|
||||||
"""Set up Actron Air integration from a config entry."""
|
"""Set up Actron Air integration from a config entry."""
|
||||||
|
|
||||||
api = ActronNeoAPI(refresh_token=entry.data[CONF_API_TOKEN])
|
api = ActronAirAPI(refresh_token=entry.data[CONF_API_TOKEN])
|
||||||
systems: list[ActronAirNeoACSystem] = []
|
systems: list[ActronAirACSystem] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
systems = await api.get_ac_systems()
|
systems = await api.get_ac_systems()
|
||||||
await api.update_status()
|
await api.update_status()
|
||||||
except ActronNeoAuthError:
|
except ActronAirAuthError:
|
||||||
_LOGGER.error("Authentication error while setting up Actron Air integration")
|
_LOGGER.error("Authentication error while setting up Actron Air integration")
|
||||||
raise
|
raise
|
||||||
except ActronNeoAPIError as err:
|
except ActronAirAPIError as err:
|
||||||
_LOGGER.error("API error while setting up Actron Air integration: %s", err)
|
_LOGGER.error("API error while setting up Actron Air integration: %s", err)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from actron_neo_api import ActronAirNeoStatus, ActronAirNeoZone
|
from actron_neo_api import ActronAirStatus, ActronAirZone
|
||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
FAN_AUTO,
|
FAN_AUTO,
|
||||||
@@ -132,7 +132,7 @@ class ActronSystemClimate(BaseClimateEntity):
|
|||||||
return self._status.max_temp
|
return self._status.max_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _status(self) -> ActronAirNeoStatus:
|
def _status(self) -> ActronAirStatus:
|
||||||
"""Get the current status from the coordinator."""
|
"""Get the current status from the coordinator."""
|
||||||
return self.coordinator.data
|
return self.coordinator.data
|
||||||
|
|
||||||
@@ -194,7 +194,7 @@ class ActronZoneClimate(BaseClimateEntity):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: ActronAirSystemCoordinator,
|
coordinator: ActronAirSystemCoordinator,
|
||||||
zone: ActronAirNeoZone,
|
zone: ActronAirZone,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize an Actron Air unit."""
|
"""Initialize an Actron Air unit."""
|
||||||
super().__init__(coordinator, zone.title)
|
super().__init__(coordinator, zone.title)
|
||||||
@@ -221,7 +221,7 @@ class ActronZoneClimate(BaseClimateEntity):
|
|||||||
return self._zone.max_temp
|
return self._zone.max_temp
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _zone(self) -> ActronAirNeoZone:
|
def _zone(self) -> ActronAirZone:
|
||||||
"""Get the current zone data from the coordinator."""
|
"""Get the current zone data from the coordinator."""
|
||||||
status = self.coordinator.data
|
status = self.coordinator.data
|
||||||
return status.zones[self._zone_id]
|
return status.zones[self._zone_id]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from actron_neo_api import ActronNeoAPI, ActronNeoAuthError
|
from actron_neo_api import ActronAirAPI, ActronAirAuthError
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_API_TOKEN
|
from homeassistant.const import CONF_API_TOKEN
|
||||||
@@ -17,7 +17,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize the config flow."""
|
"""Initialize the config flow."""
|
||||||
self._api: ActronNeoAPI | None = None
|
self._api: ActronAirAPI | None = None
|
||||||
self._device_code: str | None = None
|
self._device_code: str | None = None
|
||||||
self._user_code: str = ""
|
self._user_code: str = ""
|
||||||
self._verification_uri: str = ""
|
self._verification_uri: str = ""
|
||||||
@@ -30,10 +30,10 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Handle the initial step."""
|
"""Handle the initial step."""
|
||||||
if self._api is None:
|
if self._api is None:
|
||||||
_LOGGER.debug("Initiating device authorization")
|
_LOGGER.debug("Initiating device authorization")
|
||||||
self._api = ActronNeoAPI()
|
self._api = ActronAirAPI()
|
||||||
try:
|
try:
|
||||||
device_code_response = await self._api.request_device_code()
|
device_code_response = await self._api.request_device_code()
|
||||||
except ActronNeoAuthError as err:
|
except ActronAirAuthError as err:
|
||||||
_LOGGER.error("OAuth2 flow failed: %s", err)
|
_LOGGER.error("OAuth2 flow failed: %s", err)
|
||||||
return self.async_abort(reason="oauth2_error")
|
return self.async_abort(reason="oauth2_error")
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
try:
|
try:
|
||||||
await self._api.poll_for_token(self._device_code)
|
await self._api.poll_for_token(self._device_code)
|
||||||
_LOGGER.debug("Authorization successful")
|
_LOGGER.debug("Authorization successful")
|
||||||
except ActronNeoAuthError as ex:
|
except ActronAirAuthError as ex:
|
||||||
_LOGGER.exception("Error while waiting for device authorization")
|
_LOGGER.exception("Error while waiting for device authorization")
|
||||||
raise CannotConnect from ex
|
raise CannotConnect from ex
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class ActronAirConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
user_data = await self._api.get_user_info()
|
user_data = await self._api.get_user_info()
|
||||||
except ActronNeoAuthError as err:
|
except ActronAirAuthError as err:
|
||||||
_LOGGER.error("Error getting user info: %s", err)
|
_LOGGER.error("Error getting user info: %s", err)
|
||||||
return self.async_abort(reason="oauth2_error")
|
return self.async_abort(reason="oauth2_error")
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from actron_neo_api import ActronAirNeoACSystem, ActronAirNeoStatus, ActronNeoAPI
|
from actron_neo_api import ActronAirACSystem, ActronAirAPI, ActronAirStatus
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -23,7 +23,7 @@ ERROR_UNKNOWN = "unknown_error"
|
|||||||
class ActronAirRuntimeData:
|
class ActronAirRuntimeData:
|
||||||
"""Runtime data for the Actron Air integration."""
|
"""Runtime data for the Actron Air integration."""
|
||||||
|
|
||||||
api: ActronNeoAPI
|
api: ActronAirAPI
|
||||||
system_coordinators: dict[str, ActronAirSystemCoordinator]
|
system_coordinators: dict[str, ActronAirSystemCoordinator]
|
||||||
|
|
||||||
|
|
||||||
@@ -33,15 +33,15 @@ AUTH_ERROR_THRESHOLD = 3
|
|||||||
SCAN_INTERVAL = timedelta(seconds=30)
|
SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
|
|
||||||
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirNeoACSystem]):
|
class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirACSystem]):
|
||||||
"""System coordinator for Actron Air integration."""
|
"""System coordinator for Actron Air integration."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ActronAirConfigEntry,
|
entry: ActronAirConfigEntry,
|
||||||
api: ActronNeoAPI,
|
api: ActronAirAPI,
|
||||||
system: ActronAirNeoACSystem,
|
system: ActronAirACSystem,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the coordinator."""
|
"""Initialize the coordinator."""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
@@ -57,7 +57,7 @@ class ActronAirSystemCoordinator(DataUpdateCoordinator[ActronAirNeoACSystem]):
|
|||||||
self.status = self.api.state_manager.get_status(self.serial_number)
|
self.status = self.api.state_manager.get_status(self.serial_number)
|
||||||
self.last_seen = dt_util.utcnow()
|
self.last_seen = dt_util.utcnow()
|
||||||
|
|
||||||
async def _async_update_data(self) -> ActronAirNeoStatus:
|
async def _async_update_data(self) -> ActronAirStatus:
|
||||||
"""Fetch updates and merge incremental changes into the full state."""
|
"""Fetch updates and merge incremental changes into the full state."""
|
||||||
await self.api.update_status()
|
await self.api.update_status()
|
||||||
self.status = self.api.state_manager.get_status(self.serial_number)
|
self.status = self.api.state_manager.get_status(self.serial_number)
|
||||||
|
|||||||
@@ -12,5 +12,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/actron_air",
|
"documentation": "https://www.home-assistant.io/integrations/actron_air",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["actron-neo-api==0.1.84"]
|
"requirements": ["actron-neo-api==0.1.87"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
"title": "Actron Air OAuth2 Authorization"
|
"oauth2_error": "Failed to start OAuth2 flow"
|
||||||
},
|
|
||||||
"timeout": {
|
|
||||||
"title": "Authorization timeout",
|
|
||||||
"description": "The authorization process timed out. Please try again.",
|
|
||||||
"data": {}
|
|
||||||
},
|
|
||||||
"connection_error": {
|
|
||||||
"title": "Connection error",
|
|
||||||
"description": "Failed to connect to Actron Air. Please check your internet connection and try again.",
|
|
||||||
"data": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"progress": {
|
|
||||||
"wait_for_authorization": "To authenticate, open the following URL and login at Actron Air:\n{verification_uri}\nIf the code is not automatically copied, paste the following code to authorize the integration:\n\n```{user_code}```\n\n\nThe login attempt will time out after {expires_minutes} minutes."
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"oauth2_error": "Failed to start OAuth2 flow. Please try again later."
|
"oauth2_error": "Failed to start OAuth2 flow. Please try again later."
|
||||||
},
|
},
|
||||||
"abort": {
|
"progress": {
|
||||||
"oauth2_error": "Failed to start OAuth2 flow",
|
"wait_for_authorization": "To authenticate, open the following URL and login at Actron Air:\n{verification_uri}\nIf the code is not automatically copied, paste the following code to authorize the integration:\n\n```{user_code}```\n\n\nThe login attempt will time out after {expires_minutes} minutes."
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
},
|
||||||
|
"step": {
|
||||||
|
"connection_error": {
|
||||||
|
"data": {},
|
||||||
|
"description": "Failed to connect to Actron Air. Please check your internet connection and try again.",
|
||||||
|
"title": "Connection error"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"data": {},
|
||||||
|
"description": "The authorization process timed out. Please try again.",
|
||||||
|
"title": "Authorization timeout"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"title": "Actron Air OAuth2 Authorization"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ from homeassistant.const import (
|
|||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.selector import (
|
||||||
|
TextSelector,
|
||||||
|
TextSelectorConfig,
|
||||||
|
TextSelectorType,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
ACCOUNT_ID,
|
ACCOUNT_ID,
|
||||||
@@ -66,7 +71,15 @@ class AdaxConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle the local step."""
|
"""Handle the local step."""
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
{vol.Required(WIFI_SSID): str, vol.Required(WIFI_PSWD): str}
|
{
|
||||||
|
vol.Required(WIFI_SSID): str,
|
||||||
|
vol.Required(WIFI_PSWD): TextSelector(
|
||||||
|
TextSelectorConfig(
|
||||||
|
type=TextSelectorType.PASSWORD,
|
||||||
|
autocomplete="current-password",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
|
|||||||
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/adax",
|
"documentation": "https://www.home-assistant.io/integrations/adax",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["adax", "adax_local"],
|
"loggers": ["adax", "adax_local"],
|
||||||
"requirements": ["adax==0.4.0", "Adax-local==0.1.5"]
|
"requirements": ["adax==0.4.0", "Adax-local==0.2.0"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
)
|
)
|
||||||
from homeassistant.const import UnitOfEnergy
|
from homeassistant.const import UnitOfEnergy, UnitOfTemperature
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
@@ -20,44 +22,74 @@ from .const import CONNECTION_TYPE, DOMAIN, LOCAL
|
|||||||
from .coordinator import AdaxCloudCoordinator
|
from .coordinator import AdaxCloudCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True, frozen=True)
|
||||||
|
class AdaxSensorDescription(SensorEntityDescription):
|
||||||
|
"""Describes Adax sensor entity."""
|
||||||
|
|
||||||
|
data_key: str
|
||||||
|
|
||||||
|
|
||||||
|
SENSORS: tuple[AdaxSensorDescription, ...] = (
|
||||||
|
AdaxSensorDescription(
|
||||||
|
key="temperature",
|
||||||
|
data_key="temperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
|
),
|
||||||
|
AdaxSensorDescription(
|
||||||
|
key="energy",
|
||||||
|
data_key="energyWh",
|
||||||
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||||
|
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
|
suggested_display_precision=3,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: AdaxConfigEntry,
|
entry: AdaxConfigEntry,
|
||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the Adax energy sensors with config flow."""
|
"""Set up the Adax sensors with config flow."""
|
||||||
if entry.data.get(CONNECTION_TYPE) != LOCAL:
|
if entry.data.get(CONNECTION_TYPE) != LOCAL:
|
||||||
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
|
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
|
||||||
|
|
||||||
# Create individual energy sensors for each device
|
# Create individual energy sensors for each device
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AdaxEnergySensor(cloud_coordinator, device_id)
|
[
|
||||||
for device_id in cloud_coordinator.data
|
AdaxSensor(cloud_coordinator, entity_description, device_id)
|
||||||
|
for device_id in cloud_coordinator.data
|
||||||
|
for entity_description in SENSORS
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdaxEnergySensor(CoordinatorEntity[AdaxCloudCoordinator], SensorEntity):
|
class AdaxSensor(CoordinatorEntity[AdaxCloudCoordinator], SensorEntity):
|
||||||
"""Representation of an Adax energy sensor."""
|
"""Representation of an Adax sensor."""
|
||||||
|
|
||||||
|
entity_description: AdaxSensorDescription
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_attr_translation_key = "energy"
|
|
||||||
_attr_device_class = SensorDeviceClass.ENERGY
|
|
||||||
_attr_native_unit_of_measurement = UnitOfEnergy.WATT_HOUR
|
|
||||||
_attr_suggested_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
|
|
||||||
_attr_state_class = SensorStateClass.TOTAL_INCREASING
|
|
||||||
_attr_suggested_display_precision = 3
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
coordinator: AdaxCloudCoordinator,
|
coordinator: AdaxCloudCoordinator,
|
||||||
|
entity_description: AdaxSensorDescription,
|
||||||
device_id: str,
|
device_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the energy sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
self.entity_description = entity_description
|
||||||
self._device_id = device_id
|
self._device_id = device_id
|
||||||
room = coordinator.data[device_id]
|
room = coordinator.data[device_id]
|
||||||
|
|
||||||
self._attr_unique_id = f"{room['homeId']}_{device_id}_energy"
|
self._attr_unique_id = (
|
||||||
|
f"{room['homeId']}_{device_id}_{self.entity_description.key}"
|
||||||
|
)
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, device_id)},
|
identifiers={(DOMAIN, device_id)},
|
||||||
name=room["name"],
|
name=room["name"],
|
||||||
@@ -68,10 +100,14 @@ class AdaxEnergySensor(CoordinatorEntity[AdaxCloudCoordinator], SensorEntity):
|
|||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return True if entity is available."""
|
"""Return True if entity is available."""
|
||||||
return (
|
return (
|
||||||
super().available and "energyWh" in self.coordinator.data[self._device_id]
|
super().available
|
||||||
|
and self.entity_description.data_key
|
||||||
|
in self.coordinator.data[self._device_id]
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def native_value(self) -> int:
|
def native_value(self) -> int | float | None:
|
||||||
"""Return the native value of the sensor."""
|
"""Return the native value of the sensor."""
|
||||||
return int(self.coordinator.data[self._device_id]["energyWh"])
|
return self.coordinator.data[self._device_id].get(
|
||||||
|
self.entity_description.data_key
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"data": {
|
|
||||||
"connection_type": "Select connection type"
|
|
||||||
},
|
|
||||||
"description": "Select connection type. Local requires heaters with Bluetooth"
|
|
||||||
},
|
|
||||||
"local": {
|
|
||||||
"data": {
|
|
||||||
"wifi_ssid": "Wi-Fi SSID",
|
|
||||||
"wifi_pswd": "Wi-Fi password"
|
|
||||||
},
|
|
||||||
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue LED starts blinking before pressing Submit. Configuring heater might take some minutes."
|
|
||||||
},
|
|
||||||
"cloud": {
|
|
||||||
"data": {
|
|
||||||
"account_id": "Account ID",
|
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"heater_not_available": "Heater not available. Try to reset the heater by pressing + and OK for some seconds.",
|
"heater_not_available": "Heater not available. Try to reset the heater by pressing + and OK for some seconds.",
|
||||||
"heater_not_found": "Heater not found. Try to move the heater closer to Home Assistant computer.",
|
"heater_not_found": "Heater not found. Try to move the heater closer to Home Assistant computer.",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"cloud": {
|
||||||
|
"data": {
|
||||||
|
"account_id": "Account ID",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local": {
|
||||||
|
"data": {
|
||||||
|
"wifi_pswd": "Wi-Fi password",
|
||||||
|
"wifi_ssid": "Wi-Fi SSID"
|
||||||
|
},
|
||||||
|
"description": "Reset the heater by pressing + and OK until display shows 'Reset'. Then press and hold OK button on the heater until the blue LED starts blinking before pressing Submit. Configuring heater might take some minutes."
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"connection_type": "Select connection type"
|
||||||
|
},
|
||||||
|
"description": "Select connection type. Local requires heaters with Bluetooth"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ SERVICE_REFRESH_SCHEMA = vol.Schema(
|
|||||||
{vol.Optional(CONF_FORCE, default=False): cv.boolean}
|
{vol.Optional(CONF_FORCE, default=False): cv.boolean}
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
|
PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.UPDATE]
|
||||||
type AdGuardConfigEntry = ConfigEntry[AdGuardData]
|
type AdGuardConfigEntry = ConfigEntry[AdGuardData]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
"average_processing_speed": {
|
||||||
|
"default": "mdi:speedometer"
|
||||||
|
},
|
||||||
"dns_queries": {
|
"dns_queries": {
|
||||||
"default": "mdi:magnify"
|
"default": "mdi:magnify"
|
||||||
},
|
},
|
||||||
@@ -13,21 +16,18 @@
|
|||||||
"parental_control_blocked": {
|
"parental_control_blocked": {
|
||||||
"default": "mdi:human-male-girl"
|
"default": "mdi:human-male-girl"
|
||||||
},
|
},
|
||||||
|
"rules_count": {
|
||||||
|
"default": "mdi:counter"
|
||||||
|
},
|
||||||
"safe_browsing_blocked": {
|
"safe_browsing_blocked": {
|
||||||
"default": "mdi:shield-half-full"
|
"default": "mdi:shield-half-full"
|
||||||
},
|
},
|
||||||
"safe_searches_enforced": {
|
"safe_searches_enforced": {
|
||||||
"default": "mdi:shield-search"
|
"default": "mdi:shield-search"
|
||||||
},
|
|
||||||
"average_processing_speed": {
|
|
||||||
"default": "mdi:speedometer"
|
|
||||||
},
|
|
||||||
"rules_count": {
|
|
||||||
"default": "mdi:counter"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"protection": {
|
"filtering": {
|
||||||
"default": "mdi:shield-check",
|
"default": "mdi:shield-check",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "mdi:shield-off"
|
"off": "mdi:shield-off"
|
||||||
@@ -39,7 +39,13 @@
|
|||||||
"off": "mdi:shield-off"
|
"off": "mdi:shield-off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe_search": {
|
"protection": {
|
||||||
|
"default": "mdi:shield-check",
|
||||||
|
"state": {
|
||||||
|
"off": "mdi:shield-off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query_log": {
|
||||||
"default": "mdi:shield-check",
|
"default": "mdi:shield-check",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "mdi:shield-off"
|
"off": "mdi:shield-off"
|
||||||
@@ -51,13 +57,7 @@
|
|||||||
"off": "mdi:shield-off"
|
"off": "mdi:shield-off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filtering": {
|
"safe_search": {
|
||||||
"default": "mdi:shield-check",
|
|
||||||
"state": {
|
|
||||||
"off": "mdi:shield-off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query_log": {
|
|
||||||
"default": "mdi:shield-check",
|
"default": "mdi:shield-check",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "mdi:shield-off"
|
"off": "mdi:shield-off"
|
||||||
@@ -69,17 +69,17 @@
|
|||||||
"add_url": {
|
"add_url": {
|
||||||
"service": "mdi:link-plus"
|
"service": "mdi:link-plus"
|
||||||
},
|
},
|
||||||
"remove_url": {
|
"disable_url": {
|
||||||
"service": "mdi:link-off"
|
"service": "mdi:link-variant-off"
|
||||||
},
|
},
|
||||||
"enable_url": {
|
"enable_url": {
|
||||||
"service": "mdi:link-variant"
|
"service": "mdi:link-variant"
|
||||||
},
|
},
|
||||||
"disable_url": {
|
|
||||||
"service": "mdi:link-variant-off"
|
|
||||||
},
|
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"service": "mdi:refresh"
|
"service": "mdi:refresh"
|
||||||
|
},
|
||||||
|
"remove_url": {
|
||||||
|
"service": "mdi:link-off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["adguardhome"],
|
"loggers": ["adguardhome"],
|
||||||
"requirements": ["adguardhome==0.7.0"]
|
"requirements": ["adguardhome==0.8.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,38 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||||
"description": "Set up your AdGuard Home instance to allow monitoring and control.",
|
"existing_instance_updated": "Updated existing configuration."
|
||||||
"data": {
|
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
|
||||||
"password": "[%key:common::config_flow::data::password%]",
|
|
||||||
"port": "[%key:common::config_flow::data::port%]",
|
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
|
||||||
"ssl": "[%key:common::config_flow::data::ssl%]",
|
|
||||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"host": "The hostname or IP address of the device running your AdGuard Home."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hassio_confirm": {
|
|
||||||
"title": "AdGuard Home via Home Assistant add-on",
|
|
||||||
"description": "Do you want to configure Home Assistant to connect to the AdGuard Home provided by the add-on: {addon}?"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
"step": {
|
||||||
"existing_instance_updated": "Updated existing configuration.",
|
"hassio_confirm": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
|
"description": "Do you want to configure Home Assistant to connect to the AdGuard Home provided by the add-on: {addon}?",
|
||||||
|
"title": "AdGuard Home via Home Assistant add-on"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"port": "[%key:common::config_flow::data::port%]",
|
||||||
|
"ssl": "[%key:common::config_flow::data::ssl%]",
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"host": "The hostname or IP address of the device running your AdGuard Home."
|
||||||
|
},
|
||||||
|
"description": "Set up your AdGuard Home instance to allow monitoring and control."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
|
"average_processing_speed": {
|
||||||
|
"name": "Average processing speed"
|
||||||
|
},
|
||||||
"dns_queries": {
|
"dns_queries": {
|
||||||
"name": "DNS queries"
|
"name": "DNS queries"
|
||||||
},
|
},
|
||||||
@@ -42,94 +45,91 @@
|
|||||||
"parental_control_blocked": {
|
"parental_control_blocked": {
|
||||||
"name": "Parental control blocked"
|
"name": "Parental control blocked"
|
||||||
},
|
},
|
||||||
|
"rules_count": {
|
||||||
|
"name": "Rules count"
|
||||||
|
},
|
||||||
"safe_browsing_blocked": {
|
"safe_browsing_blocked": {
|
||||||
"name": "Safe browsing blocked"
|
"name": "Safe browsing blocked"
|
||||||
},
|
},
|
||||||
"safe_searches_enforced": {
|
"safe_searches_enforced": {
|
||||||
"name": "Safe searches enforced"
|
"name": "Safe searches enforced"
|
||||||
},
|
|
||||||
"average_processing_speed": {
|
|
||||||
"name": "Average processing speed"
|
|
||||||
},
|
|
||||||
"rules_count": {
|
|
||||||
"name": "Rules count"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"protection": {
|
"filtering": {
|
||||||
"name": "Protection"
|
"name": "Filtering"
|
||||||
},
|
},
|
||||||
"parental": {
|
"parental": {
|
||||||
"name": "Parental control"
|
"name": "Parental control"
|
||||||
},
|
},
|
||||||
"safe_search": {
|
"protection": {
|
||||||
"name": "Safe search"
|
"name": "Protection"
|
||||||
|
},
|
||||||
|
"query_log": {
|
||||||
|
"name": "Query log"
|
||||||
},
|
},
|
||||||
"safe_browsing": {
|
"safe_browsing": {
|
||||||
"name": "Safe browsing"
|
"name": "Safe browsing"
|
||||||
},
|
},
|
||||||
"filtering": {
|
"safe_search": {
|
||||||
"name": "Filtering"
|
"name": "Safe search"
|
||||||
},
|
|
||||||
"query_log": {
|
|
||||||
"name": "Query log"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"add_url": {
|
"add_url": {
|
||||||
"name": "Add URL",
|
|
||||||
"description": "Adds a new filter subscription to AdGuard Home.",
|
"description": "Adds a new filter subscription to AdGuard Home.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": {
|
"name": {
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
"description": "The name of the filter subscription.",
|
||||||
"description": "The name of the filter subscription."
|
"name": "[%key:common::config_flow::data::name%]"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"name": "[%key:common::config_flow::data::url%]",
|
"description": "The filter URL to subscribe to, containing the filter rules.",
|
||||||
"description": "The filter URL to subscribe to, containing the filter rules."
|
"name": "[%key:common::config_flow::data::url%]"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
"name": "Add URL"
|
||||||
"remove_url": {
|
|
||||||
"name": "Remove URL",
|
|
||||||
"description": "Removes a filter subscription from AdGuard Home.",
|
|
||||||
"fields": {
|
|
||||||
"url": {
|
|
||||||
"name": "[%key:common::config_flow::data::url%]",
|
|
||||||
"description": "The filter subscription URL to remove."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enable_url": {
|
|
||||||
"name": "Enable URL",
|
|
||||||
"description": "Enables a filter subscription in AdGuard Home.",
|
|
||||||
"fields": {
|
|
||||||
"url": {
|
|
||||||
"name": "[%key:common::config_flow::data::url%]",
|
|
||||||
"description": "The filter subscription URL to enable."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"disable_url": {
|
"disable_url": {
|
||||||
"name": "Disable URL",
|
|
||||||
"description": "Disables a filter subscription in AdGuard Home.",
|
"description": "Disables a filter subscription in AdGuard Home.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"url": {
|
"url": {
|
||||||
"name": "[%key:common::config_flow::data::url%]",
|
"description": "The filter subscription URL to disable.",
|
||||||
"description": "The filter subscription URL to disable."
|
"name": "[%key:common::config_flow::data::url%]"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Disable URL"
|
||||||
|
},
|
||||||
|
"enable_url": {
|
||||||
|
"description": "Enables a filter subscription in AdGuard Home.",
|
||||||
|
"fields": {
|
||||||
|
"url": {
|
||||||
|
"description": "The filter subscription URL to enable.",
|
||||||
|
"name": "[%key:common::config_flow::data::url%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Enable URL"
|
||||||
},
|
},
|
||||||
"refresh": {
|
"refresh": {
|
||||||
"name": "Refresh",
|
|
||||||
"description": "Refreshes all filter subscriptions in AdGuard Home.",
|
"description": "Refreshes all filter subscriptions in AdGuard Home.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"force": {
|
"force": {
|
||||||
"name": "Force",
|
"description": "Force update (bypasses AdGuard Home throttling), omit for a regular refresh.",
|
||||||
"description": "Force update (bypasses AdGuard Home throttling), omit for a regular refresh."
|
"name": "Force"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Refresh"
|
||||||
|
},
|
||||||
|
"remove_url": {
|
||||||
|
"description": "Removes a filter subscription from AdGuard Home.",
|
||||||
|
"fields": {
|
||||||
|
"url": {
|
||||||
|
"description": "The filter subscription URL to remove.",
|
||||||
|
"name": "[%key:common::config_flow::data::url%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Remove URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
homeassistant/components/adguard/update.py
Normal file
71
homeassistant/components/adguard/update.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""AdGuard Home Update platform."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from adguardhome import AdGuardHomeError
|
||||||
|
|
||||||
|
from homeassistant.components.update import UpdateEntity, UpdateEntityFeature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
|
from . import AdGuardConfigEntry, AdGuardData
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entity import AdGuardHomeEntity
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=300)
|
||||||
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: AdGuardConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up AdGuard Home update entity based on a config entry."""
|
||||||
|
data = entry.runtime_data
|
||||||
|
|
||||||
|
if (await data.client.update.update_available()).disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
async_add_entities([AdGuardHomeUpdate(data, entry)], True)
|
||||||
|
|
||||||
|
|
||||||
|
class AdGuardHomeUpdate(AdGuardHomeEntity, UpdateEntity):
|
||||||
|
"""Defines an AdGuard Home update."""
|
||||||
|
|
||||||
|
_attr_supported_features = UpdateEntityFeature.INSTALL
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: AdGuardData,
|
||||||
|
entry: AdGuardConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize AdGuard Home update."""
|
||||||
|
super().__init__(data, entry)
|
||||||
|
|
||||||
|
self._attr_unique_id = "_".join(
|
||||||
|
[DOMAIN, self.adguard.host, str(self.adguard.port), "update"]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _adguard_update(self) -> None:
|
||||||
|
"""Update AdGuard Home entity."""
|
||||||
|
value = await self.adguard.update.update_available()
|
||||||
|
self._attr_installed_version = self.data.version
|
||||||
|
self._attr_latest_version = value.new_version
|
||||||
|
self._attr_release_summary = value.announcement
|
||||||
|
self._attr_release_url = value.announcement_url
|
||||||
|
|
||||||
|
async def async_install(
|
||||||
|
self, version: str | None, backup: bool, **kwargs: Any
|
||||||
|
) -> None:
|
||||||
|
"""Install latest update."""
|
||||||
|
try:
|
||||||
|
await self.adguard.update.begin_update()
|
||||||
|
except AdGuardHomeError as err:
|
||||||
|
raise HomeAssistantError(f"Failed to install update: {err}") from err
|
||||||
|
self.hass.config_entries.async_schedule_reload(self._entry.entry_id)
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"write_data_by_name": {
|
"write_data_by_name": {
|
||||||
"name": "Write data by name",
|
|
||||||
"description": "Write a value to the connected ADS device.",
|
"description": "Write a value to the connected ADS device.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"adsvar": {
|
|
||||||
"name": "ADS variable",
|
|
||||||
"description": "The name of the variable to write to."
|
|
||||||
},
|
|
||||||
"adstype": {
|
"adstype": {
|
||||||
"name": "ADS type",
|
"description": "The data type of the variable to write to.",
|
||||||
"description": "The data type of the variable to write to."
|
"name": "ADS type"
|
||||||
|
},
|
||||||
|
"adsvar": {
|
||||||
|
"description": "The name of the variable to write to.",
|
||||||
|
"name": "ADS variable"
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"name": "Value",
|
"description": "The value to write to the variable.",
|
||||||
"description": "The value to write to the variable."
|
"name": "Value"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Write data by name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
},
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
@@ -19,14 +19,14 @@
|
|||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"set_time_to": {
|
"set_time_to": {
|
||||||
"name": "Set time to",
|
|
||||||
"description": "Controls timers to turn the system on or off after a set number of minutes.",
|
"description": "Controls timers to turn the system on or off after a set number of minutes.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"minutes": {
|
"minutes": {
|
||||||
"name": "Minutes",
|
"description": "Minutes until action.",
|
||||||
"description": "Minutes until action."
|
"name": "Minutes"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Set time to"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,57 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"services": {
|
|
||||||
"add_tracking": {
|
|
||||||
"name": "Add tracking",
|
|
||||||
"description": "Adds a new tracking number to Aftership.",
|
|
||||||
"fields": {
|
|
||||||
"tracking_number": {
|
|
||||||
"name": "Tracking number",
|
|
||||||
"description": "Tracking number for the new tracking."
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "Title",
|
|
||||||
"description": "A custom title for the new tracking."
|
|
||||||
},
|
|
||||||
"slug": {
|
|
||||||
"name": "Slug",
|
|
||||||
"description": "Slug (carrier) of the new tracking."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"remove_tracking": {
|
|
||||||
"name": "Remove tracking",
|
|
||||||
"description": "Removes a tracking number from Aftership.",
|
|
||||||
"fields": {
|
|
||||||
"tracking_number": {
|
|
||||||
"name": "[%key:component::aftership::services::add_tracking::fields::tracking_number::name%]",
|
|
||||||
"description": "Tracking number of the tracking to remove."
|
|
||||||
},
|
|
||||||
"slug": {
|
|
||||||
"name": "[%key:component::aftership::services::add_tracking::fields::slug::name%]",
|
|
||||||
"description": "Slug (carrier) of the tracking to remove."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
"deprecated_yaml_import_issue_cannot_connect": {
|
"deprecated_yaml_import_issue_cannot_connect": {
|
||||||
"title": "The {integration_title} YAML configuration import failed",
|
"description": "Configuring {integration_title} using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually.",
|
||||||
"description": "Configuring {integration_title} using YAML is being removed but there was a connection error importing your YAML configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the {integration_title} YAML configuration from your configuration.yaml file and continue to [set up the integration]({url}) manually."
|
"title": "The {integration_title} YAML configuration import failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"add_tracking": {
|
||||||
|
"description": "Adds a new tracking number to Aftership.",
|
||||||
|
"fields": {
|
||||||
|
"slug": {
|
||||||
|
"description": "Slug (carrier) of the new tracking.",
|
||||||
|
"name": "Slug"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"description": "A custom title for the new tracking.",
|
||||||
|
"name": "Title"
|
||||||
|
},
|
||||||
|
"tracking_number": {
|
||||||
|
"description": "Tracking number for the new tracking.",
|
||||||
|
"name": "Tracking number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Add tracking"
|
||||||
|
},
|
||||||
|
"remove_tracking": {
|
||||||
|
"description": "Removes a tracking number from Aftership.",
|
||||||
|
"fields": {
|
||||||
|
"slug": {
|
||||||
|
"description": "Slug (carrier) of the tracking to remove.",
|
||||||
|
"name": "[%key:component::aftership::services::add_tracking::fields::slug::name%]"
|
||||||
|
},
|
||||||
|
"tracking_number": {
|
||||||
|
"description": "Tracking number of the tracking to remove.",
|
||||||
|
"name": "[%key:component::aftership::services::add_tracking::fields::tracking_number::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Remove tracking"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
|
"disable_alerts": {
|
||||||
|
"service": "mdi:bell-off"
|
||||||
|
},
|
||||||
|
"enable_alerts": {
|
||||||
|
"service": "mdi:bell-alert"
|
||||||
|
},
|
||||||
|
"snapshot": {
|
||||||
|
"service": "mdi:camera"
|
||||||
|
},
|
||||||
"start_recording": {
|
"start_recording": {
|
||||||
"service": "mdi:record-rec"
|
"service": "mdi:record-rec"
|
||||||
},
|
},
|
||||||
"stop_recording": {
|
"stop_recording": {
|
||||||
"service": "mdi:stop"
|
"service": "mdi:stop"
|
||||||
},
|
|
||||||
"enable_alerts": {
|
|
||||||
"service": "mdi:bell-alert"
|
|
||||||
},
|
|
||||||
"disable_alerts": {
|
|
||||||
"service": "mdi:bell-off"
|
|
||||||
},
|
|
||||||
"snapshot": {
|
|
||||||
"service": "mdi:camera"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"title": "Set up Agent DVR",
|
|
||||||
"data": {
|
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
|
||||||
"port": "[%key:common::config_flow::data::port%]"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"host": "The IP address of the Agent DVR server."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"host": "The IP address of the Agent DVR server."
|
||||||
|
},
|
||||||
|
"title": "Set up Agent DVR"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"start_recording": {
|
"disable_alerts": {
|
||||||
"name": "Start recording",
|
"description": "Disables alerts.",
|
||||||
"description": "Enables continuous recording."
|
"name": "Disable alerts"
|
||||||
},
|
|
||||||
"stop_recording": {
|
|
||||||
"name": "Stop recording",
|
|
||||||
"description": "Disables continuous recording."
|
|
||||||
},
|
},
|
||||||
"enable_alerts": {
|
"enable_alerts": {
|
||||||
"name": "Enable alerts",
|
"description": "Enables alerts.",
|
||||||
"description": "Enables alerts."
|
"name": "Enable alerts"
|
||||||
},
|
|
||||||
"disable_alerts": {
|
|
||||||
"name": "Disable alerts",
|
|
||||||
"description": "Disables alerts."
|
|
||||||
},
|
},
|
||||||
"snapshot": {
|
"snapshot": {
|
||||||
"name": "Snapshot",
|
"description": "Takes a photo.",
|
||||||
"description": "Takes a photo."
|
"name": "Snapshot"
|
||||||
|
},
|
||||||
|
"start_recording": {
|
||||||
|
"description": "Enables continuous recording.",
|
||||||
|
"name": "Start recording"
|
||||||
|
},
|
||||||
|
"stop_recording": {
|
||||||
|
"description": "Disables continuous recording.",
|
||||||
|
"name": "Stop recording"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ __all__ = [
|
|||||||
"GenImageTaskResult",
|
"GenImageTaskResult",
|
||||||
"async_generate_data",
|
"async_generate_data",
|
||||||
"async_generate_image",
|
"async_generate_image",
|
||||||
"async_setup",
|
|
||||||
"async_setup_entry",
|
|
||||||
"async_unload_entry",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ generate_data:
|
|||||||
media:
|
media:
|
||||||
accept:
|
accept:
|
||||||
- "*"
|
- "*"
|
||||||
|
multiple: true
|
||||||
generate_image:
|
generate_image:
|
||||||
fields:
|
fields:
|
||||||
task_name:
|
task_name:
|
||||||
@@ -57,3 +58,4 @@ generate_image:
|
|||||||
media:
|
media:
|
||||||
accept:
|
accept:
|
||||||
- "*"
|
- "*"
|
||||||
|
multiple: true
|
||||||
|
|||||||
@@ -1,52 +1,52 @@
|
|||||||
{
|
{
|
||||||
"services": {
|
"services": {
|
||||||
"generate_data": {
|
"generate_data": {
|
||||||
"name": "Generate data",
|
|
||||||
"description": "Uses AI to run a task that generates data.",
|
"description": "Uses AI to run a task that generates data.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"task_name": {
|
"attachments": {
|
||||||
"name": "Task name",
|
"description": "List of files to attach for multi-modal AI analysis.",
|
||||||
"description": "Name of the task."
|
"name": "Attachments"
|
||||||
},
|
|
||||||
"instructions": {
|
|
||||||
"name": "Instructions",
|
|
||||||
"description": "Instructions on what needs to be done."
|
|
||||||
},
|
},
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"name": "Entity ID",
|
"description": "Entity ID to run the task on. If not provided, the preferred entity will be used.",
|
||||||
"description": "Entity ID to run the task on. If not provided, the preferred entity will be used."
|
"name": "Entity ID"
|
||||||
|
},
|
||||||
|
"instructions": {
|
||||||
|
"description": "Instructions on what needs to be done.",
|
||||||
|
"name": "Instructions"
|
||||||
},
|
},
|
||||||
"structure": {
|
"structure": {
|
||||||
"name": "Structured output",
|
"description": "When set, the AI Task will output fields with this in structure. The structure is a dictionary where the keys are the field names and the values contain a 'description', a 'selector', and an optional 'required' field.",
|
||||||
"description": "When set, the AI Task will output fields with this in structure. The structure is a dictionary where the keys are the field names and the values contain a 'description', a 'selector', and an optional 'required' field."
|
"name": "Structured output"
|
||||||
},
|
},
|
||||||
"attachments": {
|
"task_name": {
|
||||||
"name": "Attachments",
|
"description": "Name of the task.",
|
||||||
"description": "List of files to attach for multi-modal AI analysis."
|
"name": "Task name"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Generate data"
|
||||||
},
|
},
|
||||||
"generate_image": {
|
"generate_image": {
|
||||||
"name": "Generate image",
|
|
||||||
"description": "Uses AI to generate image.",
|
"description": "Uses AI to generate image.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"task_name": {
|
"attachments": {
|
||||||
"name": "Task name",
|
"description": "List of files to attach for using as references.",
|
||||||
"description": "Name of the task."
|
"name": "Attachments"
|
||||||
},
|
|
||||||
"instructions": {
|
|
||||||
"name": "Instructions",
|
|
||||||
"description": "Instructions that explains the image to be generated."
|
|
||||||
},
|
},
|
||||||
"entity_id": {
|
"entity_id": {
|
||||||
"name": "Entity ID",
|
"description": "Entity ID to run the task on.",
|
||||||
"description": "Entity ID to run the task on."
|
"name": "Entity ID"
|
||||||
},
|
},
|
||||||
"attachments": {
|
"instructions": {
|
||||||
"name": "Attachments",
|
"description": "Instructions that explains the image to be generated.",
|
||||||
"description": "List of files to attach for using as references."
|
"name": "Instructions"
|
||||||
|
},
|
||||||
|
"task_name": {
|
||||||
|
"description": "Name of the task.",
|
||||||
|
"name": "Task name"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Generate image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
"led_bar_brightness": {
|
"display_brightness": {
|
||||||
"default": "mdi:brightness-percent"
|
"default": "mdi:brightness-percent"
|
||||||
},
|
},
|
||||||
"display_brightness": {
|
"led_bar_brightness": {
|
||||||
"default": "mdi:brightness-percent"
|
"default": "mdi:brightness-percent"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
|
"co2_automatic_baseline_calibration": {
|
||||||
|
"default": "mdi:molecule-co2"
|
||||||
|
},
|
||||||
"configuration_control": {
|
"configuration_control": {
|
||||||
"default": "mdi:cloud-cog"
|
"default": "mdi:cloud-cog"
|
||||||
},
|
},
|
||||||
@@ -31,23 +34,11 @@
|
|||||||
},
|
},
|
||||||
"voc_index_learning_time_offset": {
|
"voc_index_learning_time_offset": {
|
||||||
"default": "mdi:clock-outline"
|
"default": "mdi:clock-outline"
|
||||||
},
|
|
||||||
"co2_automatic_baseline_calibration": {
|
|
||||||
"default": "mdi:molecule-co2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"total_volatile_organic_component_index": {
|
"co2_automatic_baseline_calibration": {
|
||||||
"default": "mdi:molecule"
|
"default": "mdi:molecule-co2"
|
||||||
},
|
|
||||||
"nitrogen_index": {
|
|
||||||
"default": "mdi:molecule"
|
|
||||||
},
|
|
||||||
"pm003_count": {
|
|
||||||
"default": "mdi:blur"
|
|
||||||
},
|
|
||||||
"led_bar_brightness": {
|
|
||||||
"default": "mdi:brightness-percent"
|
|
||||||
},
|
},
|
||||||
"display_brightness": {
|
"display_brightness": {
|
||||||
"default": "mdi:brightness-percent"
|
"default": "mdi:brightness-percent"
|
||||||
@@ -55,17 +46,26 @@
|
|||||||
"display_temperature_unit": {
|
"display_temperature_unit": {
|
||||||
"default": "mdi:thermometer-lines"
|
"default": "mdi:thermometer-lines"
|
||||||
},
|
},
|
||||||
|
"led_bar_brightness": {
|
||||||
|
"default": "mdi:brightness-percent"
|
||||||
|
},
|
||||||
"led_bar_mode": {
|
"led_bar_mode": {
|
||||||
"default": "mdi:led-strip"
|
"default": "mdi:led-strip"
|
||||||
},
|
},
|
||||||
|
"nitrogen_index": {
|
||||||
|
"default": "mdi:molecule"
|
||||||
|
},
|
||||||
"nox_index_learning_time_offset": {
|
"nox_index_learning_time_offset": {
|
||||||
"default": "mdi:clock-outline"
|
"default": "mdi:clock-outline"
|
||||||
},
|
},
|
||||||
|
"pm003_count": {
|
||||||
|
"default": "mdi:blur"
|
||||||
|
},
|
||||||
|
"total_volatile_organic_component_index": {
|
||||||
|
"default": "mdi:molecule"
|
||||||
|
},
|
||||||
"voc_index_learning_time_offset": {
|
"voc_index_learning_time_offset": {
|
||||||
"default": "mdi:clock-outline"
|
"default": "mdi:clock-outline"
|
||||||
},
|
|
||||||
"co2_automatic_baseline_calibration": {
|
|
||||||
"default": "mdi:molecule-co2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"flow_title": "{model}",
|
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"data": {
|
|
||||||
"host": "[%key:common::config_flow::data::host%]"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"host": "The hostname or IP address of the Airgradient device."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"discovery_confirm": {
|
|
||||||
"description": "Do you want to set up {model}?"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
@@ -24,6 +10,20 @@
|
|||||||
"error": {
|
"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%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"flow_title": "{model}",
|
||||||
|
"step": {
|
||||||
|
"discovery_confirm": {
|
||||||
|
"description": "Do you want to set up {model}?"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"host": "The hostname or IP address of the Airgradient device."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -36,14 +36,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
"led_bar_brightness": {
|
|
||||||
"name": "LED bar brightness"
|
|
||||||
},
|
|
||||||
"display_brightness": {
|
"display_brightness": {
|
||||||
"name": "Display brightness"
|
"name": "Display brightness"
|
||||||
|
},
|
||||||
|
"led_bar_brightness": {
|
||||||
|
"name": "LED bar brightness"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
|
"co2_automatic_baseline_calibration": {
|
||||||
|
"name": "CO2 automatic baseline duration",
|
||||||
|
"state": {
|
||||||
|
"0": "[%key:common::state::off%]",
|
||||||
|
"1": "1 day",
|
||||||
|
"8": "8 days",
|
||||||
|
"30": "30 days",
|
||||||
|
"90": "90 days",
|
||||||
|
"180": "180 days"
|
||||||
|
}
|
||||||
|
},
|
||||||
"configuration_control": {
|
"configuration_control": {
|
||||||
"name": "Configuration source",
|
"name": "Configuration source",
|
||||||
"state": {
|
"state": {
|
||||||
@@ -51,13 +62,6 @@
|
|||||||
"local": "Local"
|
"local": "Local"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"display_temperature_unit": {
|
|
||||||
"name": "Display temperature unit",
|
|
||||||
"state": {
|
|
||||||
"c": "Celsius",
|
|
||||||
"f": "Fahrenheit"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"display_pm_standard": {
|
"display_pm_standard": {
|
||||||
"name": "Display PM standard",
|
"name": "Display PM standard",
|
||||||
"state": {
|
"state": {
|
||||||
@@ -65,11 +69,18 @@
|
|||||||
"us_aqi": "US AQI"
|
"us_aqi": "US AQI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"display_temperature_unit": {
|
||||||
|
"name": "Display temperature unit",
|
||||||
|
"state": {
|
||||||
|
"c": "Celsius",
|
||||||
|
"f": "Fahrenheit"
|
||||||
|
}
|
||||||
|
},
|
||||||
"led_bar_mode": {
|
"led_bar_mode": {
|
||||||
"name": "LED bar mode",
|
"name": "LED bar mode",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:common::state::off%]",
|
|
||||||
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
"pm": "Particulate matter"
|
"pm": "Particulate matter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -92,37 +103,14 @@
|
|||||||
"360": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::state::360%]",
|
"360": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::state::360%]",
|
||||||
"720": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::state::720%]"
|
"720": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::state::720%]"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"co2_automatic_baseline_calibration": {
|
|
||||||
"name": "CO2 automatic baseline duration",
|
|
||||||
"state": {
|
|
||||||
"1": "1 day",
|
|
||||||
"8": "8 days",
|
|
||||||
"30": "30 days",
|
|
||||||
"90": "90 days",
|
|
||||||
"180": "180 days",
|
|
||||||
"0": "[%key:common::state::off%]"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"total_volatile_organic_component_index": {
|
"co2_automatic_baseline_calibration_days": {
|
||||||
"name": "VOC index"
|
"name": "Carbon dioxide automatic baseline calibration"
|
||||||
},
|
},
|
||||||
"nitrogen_index": {
|
"display_brightness": {
|
||||||
"name": "NOx index"
|
"name": "[%key:component::airgradient::entity::number::display_brightness::name%]"
|
||||||
},
|
|
||||||
"pm003_count": {
|
|
||||||
"name": "PM0.3"
|
|
||||||
},
|
|
||||||
"raw_total_volatile_organic_component": {
|
|
||||||
"name": "Raw VOC"
|
|
||||||
},
|
|
||||||
"raw_nitrogen": {
|
|
||||||
"name": "Raw NOx"
|
|
||||||
},
|
|
||||||
"raw_pm02": {
|
|
||||||
"name": "Raw PM2.5"
|
|
||||||
},
|
},
|
||||||
"display_pm_standard": {
|
"display_pm_standard": {
|
||||||
"name": "[%key:component::airgradient::entity::select::display_pm_standard::name%]",
|
"name": "[%key:component::airgradient::entity::select::display_pm_standard::name%]",
|
||||||
@@ -131,26 +119,6 @@
|
|||||||
"us_aqi": "[%key:component::airgradient::entity::select::display_pm_standard::state::us_aqi%]"
|
"us_aqi": "[%key:component::airgradient::entity::select::display_pm_standard::state::us_aqi%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"co2_automatic_baseline_calibration_days": {
|
|
||||||
"name": "Carbon dioxide automatic baseline calibration"
|
|
||||||
},
|
|
||||||
"nox_learning_offset": {
|
|
||||||
"name": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::name%]"
|
|
||||||
},
|
|
||||||
"tvoc_learning_offset": {
|
|
||||||
"name": "[%key:component::airgradient::entity::select::voc_index_learning_time_offset::name%]"
|
|
||||||
},
|
|
||||||
"led_bar_mode": {
|
|
||||||
"name": "[%key:component::airgradient::entity::select::led_bar_mode::name%]",
|
|
||||||
"state": {
|
|
||||||
"off": "[%key:common::state::off%]",
|
|
||||||
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
|
||||||
"pm": "[%key:component::airgradient::entity::select::led_bar_mode::state::pm%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"led_bar_brightness": {
|
|
||||||
"name": "[%key:component::airgradient::entity::number::led_bar_brightness::name%]"
|
|
||||||
},
|
|
||||||
"display_temperature_unit": {
|
"display_temperature_unit": {
|
||||||
"name": "[%key:component::airgradient::entity::select::display_temperature_unit::name%]",
|
"name": "[%key:component::airgradient::entity::select::display_temperature_unit::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
@@ -158,8 +126,40 @@
|
|||||||
"f": "[%key:component::airgradient::entity::select::display_temperature_unit::state::f%]"
|
"f": "[%key:component::airgradient::entity::select::display_temperature_unit::state::f%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"display_brightness": {
|
"led_bar_brightness": {
|
||||||
"name": "[%key:component::airgradient::entity::number::display_brightness::name%]"
|
"name": "[%key:component::airgradient::entity::number::led_bar_brightness::name%]"
|
||||||
|
},
|
||||||
|
"led_bar_mode": {
|
||||||
|
"name": "[%key:component::airgradient::entity::select::led_bar_mode::name%]",
|
||||||
|
"state": {
|
||||||
|
"co2": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"pm": "[%key:component::airgradient::entity::select::led_bar_mode::state::pm%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nitrogen_index": {
|
||||||
|
"name": "NOx index"
|
||||||
|
},
|
||||||
|
"nox_learning_offset": {
|
||||||
|
"name": "[%key:component::airgradient::entity::select::nox_index_learning_time_offset::name%]"
|
||||||
|
},
|
||||||
|
"pm003_count": {
|
||||||
|
"name": "PM0.3"
|
||||||
|
},
|
||||||
|
"raw_nitrogen": {
|
||||||
|
"name": "Raw NOx"
|
||||||
|
},
|
||||||
|
"raw_pm02": {
|
||||||
|
"name": "Raw PM2.5"
|
||||||
|
},
|
||||||
|
"raw_total_volatile_organic_component": {
|
||||||
|
"name": "Raw VOC"
|
||||||
|
},
|
||||||
|
"total_volatile_organic_component_index": {
|
||||||
|
"name": "VOC index"
|
||||||
|
},
|
||||||
|
"tvoc_learning_offset": {
|
||||||
|
"name": "[%key:component::airgradient::entity::select::voc_index_learning_time_offset::name%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"description": "To generate API key go to {developer_registration_url}",
|
|
||||||
"data": {
|
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
|
||||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
|
||||||
"longitude": "[%key:common::config_flow::data::longitude%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"wrong_location": "No Airly measuring stations in this area.",
|
|
||||||
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||||
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
|
"wrong_location": "[%key:component::airly::config::error::wrong_location%]"
|
||||||
}
|
},
|
||||||
},
|
"error": {
|
||||||
"system_health": {
|
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
||||||
"info": {
|
"wrong_location": "No Airly measuring stations in this area."
|
||||||
"can_reach_server": "Reach Airly server",
|
},
|
||||||
"requests_remaining": "Remaining allowed requests",
|
"step": {
|
||||||
"requests_per_day": "Allowed requests per day"
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
|
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||||
|
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||||
|
"name": "[%key:common::config_flow::data::name%]"
|
||||||
|
},
|
||||||
|
"description": "To generate API key go to {developer_registration_url}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -38,11 +31,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
"update_error": {
|
|
||||||
"message": "An error occurred while retrieving data from the Airly API for {entry}: {error}"
|
|
||||||
},
|
|
||||||
"no_station": {
|
"no_station": {
|
||||||
"message": "An error occurred while retrieving data from the Airly API for {entry}: no measuring stations in this area"
|
"message": "An error occurred while retrieving data from the Airly API for {entry}: no measuring stations in this area"
|
||||||
|
},
|
||||||
|
"update_error": {
|
||||||
|
"message": "An error occurred while retrieving data from the Airly API for {entry}: {error}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system_health": {
|
||||||
|
"info": {
|
||||||
|
"can_reach_server": "Reach Airly server",
|
||||||
|
"requests_per_day": "Allowed requests per day",
|
||||||
|
"requests_remaining": "Remaining allowed requests"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ from .const import DOMAIN
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Documentation URL for API key generation
|
||||||
|
_API_KEY_URL = "https://docs.airnowapi.org/account/request/"
|
||||||
|
|
||||||
|
|
||||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool:
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> bool:
|
||||||
"""Validate the user input allows us to connect.
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
@@ -114,6 +118,7 @@ class AirNowConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
description_placeholders={"api_key_url": _API_KEY_URL},
|
||||||
errors=errors,
|
errors=errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
"aqi": {
|
"aqi": {
|
||||||
"default": "mdi:blur"
|
"default": "mdi:blur"
|
||||||
},
|
},
|
||||||
|
"o3": {
|
||||||
|
"default": "mdi:blur"
|
||||||
|
},
|
||||||
"pm10": {
|
"pm10": {
|
||||||
"default": "mdi:blur"
|
"default": "mdi:blur"
|
||||||
},
|
},
|
||||||
"pm25": {
|
"pm25": {
|
||||||
"default": "mdi:blur"
|
"default": "mdi:blur"
|
||||||
},
|
},
|
||||||
"o3": {
|
|
||||||
"default": "mdi:blur"
|
|
||||||
},
|
|
||||||
"station": {
|
"station": {
|
||||||
"default": "mdi:blur"
|
"default": "mdi:blur"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
"description": "To generate API key go to https://docs.airnowapi.org/account/request/",
|
|
||||||
"data": {
|
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
|
||||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
|
||||||
"longitude": "[%key:common::config_flow::data::longitude%]",
|
|
||||||
"radius": "Station radius (miles; optional)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
@@ -17,16 +9,15 @@
|
|||||||
"invalid_location": "No results found for that location, try changing the location or station radius.",
|
"invalid_location": "No results found for that location, try changing the location or station radius.",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"radius": "Station radius (miles)"
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
}
|
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||||
|
"longitude": "[%key:common::config_flow::data::longitude%]",
|
||||||
|
"radius": "Station radius (miles; optional)"
|
||||||
|
},
|
||||||
|
"description": "To generate API key go to {api_key_url}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -43,5 +34,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"radius": "Station radius (miles)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
homeassistant/components/airobot/__init__.py
Normal file
29
homeassistant/components/airobot/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""The Airobot integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .coordinator import AirobotConfigEntry, AirobotDataUpdateCoordinator
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.CLIMATE]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: AirobotConfigEntry) -> bool:
|
||||||
|
"""Set up Airobot from a config entry."""
|
||||||
|
coordinator = AirobotDataUpdateCoordinator(hass, entry)
|
||||||
|
|
||||||
|
# Fetch initial data so we have data when entities subscribe
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
entry.runtime_data = coordinator
|
||||||
|
|
||||||
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: AirobotConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
151
homeassistant/components/airobot/climate.py
Normal file
151
homeassistant/components/airobot/climate.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
"""Climate platform for Airobot thermostat."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pyairobotrest.const import (
|
||||||
|
MODE_AWAY,
|
||||||
|
MODE_HOME,
|
||||||
|
SETPOINT_TEMP_MAX,
|
||||||
|
SETPOINT_TEMP_MIN,
|
||||||
|
)
|
||||||
|
from pyairobotrest.exceptions import AirobotError
|
||||||
|
from pyairobotrest.models import ThermostatSettings, ThermostatStatus
|
||||||
|
|
||||||
|
from homeassistant.components.climate import (
|
||||||
|
PRESET_AWAY,
|
||||||
|
PRESET_BOOST,
|
||||||
|
PRESET_HOME,
|
||||||
|
ClimateEntity,
|
||||||
|
ClimateEntityFeature,
|
||||||
|
HVACAction,
|
||||||
|
HVACMode,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
|
from . import AirobotConfigEntry
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entity import AirobotEntity
|
||||||
|
|
||||||
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
|
_PRESET_MODE_2_MODE = {
|
||||||
|
PRESET_AWAY: MODE_AWAY,
|
||||||
|
PRESET_HOME: MODE_HOME,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: AirobotConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up Airobot climate platform."""
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
async_add_entities([AirobotClimate(coordinator)])
|
||||||
|
|
||||||
|
|
||||||
|
class AirobotClimate(AirobotEntity, ClimateEntity):
|
||||||
|
"""Representation of an Airobot thermostat."""
|
||||||
|
|
||||||
|
_attr_name = None
|
||||||
|
_attr_translation_key = "thermostat"
|
||||||
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
|
_attr_hvac_modes = [HVACMode.HEAT]
|
||||||
|
_attr_preset_modes = [PRESET_HOME, PRESET_AWAY, PRESET_BOOST]
|
||||||
|
_attr_supported_features = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||||
|
)
|
||||||
|
_attr_min_temp = SETPOINT_TEMP_MIN
|
||||||
|
_attr_max_temp = SETPOINT_TEMP_MAX
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _status(self) -> ThermostatStatus:
|
||||||
|
"""Get status from coordinator data."""
|
||||||
|
return self.coordinator.data.status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _settings(self) -> ThermostatSettings:
|
||||||
|
"""Get settings from coordinator data."""
|
||||||
|
return self.coordinator.data.settings
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_temperature(self) -> float | None:
|
||||||
|
"""Return the current temperature."""
|
||||||
|
return self._status.temp_air
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_temperature(self) -> float | None:
|
||||||
|
"""Return the target temperature."""
|
||||||
|
if self._settings.is_home_mode:
|
||||||
|
return self._settings.setpoint_temp
|
||||||
|
return self._settings.setpoint_temp_away
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> HVACMode:
|
||||||
|
"""Return current HVAC mode."""
|
||||||
|
if self._status.is_heating:
|
||||||
|
return HVACMode.HEAT
|
||||||
|
return HVACMode.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction:
|
||||||
|
"""Return current HVAC action."""
|
||||||
|
if self._status.is_heating:
|
||||||
|
return HVACAction.HEATING
|
||||||
|
return HVACAction.IDLE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preset_mode(self) -> str | None:
|
||||||
|
"""Return current preset mode."""
|
||||||
|
if self._settings.setting_flags.boost_enabled:
|
||||||
|
return PRESET_BOOST
|
||||||
|
if self._settings.is_home_mode:
|
||||||
|
return PRESET_HOME
|
||||||
|
return PRESET_AWAY
|
||||||
|
|
||||||
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
|
"""Set new target temperature."""
|
||||||
|
temperature = kwargs[ATTR_TEMPERATURE]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self._settings.is_home_mode:
|
||||||
|
await self.coordinator.client.set_home_temperature(float(temperature))
|
||||||
|
else:
|
||||||
|
await self.coordinator.client.set_away_temperature(float(temperature))
|
||||||
|
except AirobotError as err:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="set_temperature_failed",
|
||||||
|
translation_placeholders={"temperature": str(temperature)},
|
||||||
|
) from err
|
||||||
|
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set new preset mode."""
|
||||||
|
try:
|
||||||
|
if preset_mode == PRESET_BOOST:
|
||||||
|
# Enable boost mode
|
||||||
|
if not self._settings.setting_flags.boost_enabled:
|
||||||
|
await self.coordinator.client.set_boost_mode(True)
|
||||||
|
else:
|
||||||
|
# Disable boost mode if it's enabled
|
||||||
|
if self._settings.setting_flags.boost_enabled:
|
||||||
|
await self.coordinator.client.set_boost_mode(False)
|
||||||
|
|
||||||
|
# Set the mode (HOME or AWAY)
|
||||||
|
await self.coordinator.client.set_mode(_PRESET_MODE_2_MODE[preset_mode])
|
||||||
|
|
||||||
|
except AirobotError as err:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="set_preset_mode_failed",
|
||||||
|
translation_placeholders={"preset_mode": preset_mode},
|
||||||
|
) from err
|
||||||
|
|
||||||
|
await self.coordinator.async_request_refresh()
|
||||||
183
homeassistant/components/airobot/config_flow.py
Normal file
183
homeassistant/components/airobot/config_flow.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
"""Config flow for the Airobot integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pyairobotrest import AirobotClient
|
||||||
|
from pyairobotrest.exceptions import (
|
||||||
|
AirobotAuthError,
|
||||||
|
AirobotConnectionError,
|
||||||
|
AirobotError,
|
||||||
|
AirobotTimeoutError,
|
||||||
|
)
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow as BaseConfigFlow, ConfigFlowResult
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): str,
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeviceInfo:
|
||||||
|
"""Device information."""
|
||||||
|
|
||||||
|
title: str
|
||||||
|
device_id: str
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> DeviceInfo:
|
||||||
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
|
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||||
|
"""
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
client = AirobotClient(
|
||||||
|
host=data[CONF_HOST],
|
||||||
|
username=data[CONF_USERNAME],
|
||||||
|
password=data[CONF_PASSWORD],
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try to fetch data to validate connection and authentication
|
||||||
|
status = await client.get_statuses()
|
||||||
|
settings = await client.get_settings()
|
||||||
|
except AirobotAuthError as err:
|
||||||
|
raise InvalidAuth from err
|
||||||
|
except (AirobotConnectionError, AirobotTimeoutError, AirobotError) as err:
|
||||||
|
raise CannotConnect from err
|
||||||
|
|
||||||
|
# Use device name or device ID as title
|
||||||
|
title = settings.device_name or status.device_id
|
||||||
|
|
||||||
|
return DeviceInfo(title=title, device_id=status.device_id)
|
||||||
|
|
||||||
|
|
||||||
|
class AirobotConfigFlow(BaseConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Airobot."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
MINOR_VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""Initialize the config flow."""
|
||||||
|
self._discovered_host: str | None = None
|
||||||
|
self._discovered_mac: str | None = None
|
||||||
|
self._discovered_device_id: str | None = None
|
||||||
|
|
||||||
|
async def async_step_dhcp(
|
||||||
|
self, discovery_info: DhcpServiceInfo
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle DHCP discovery."""
|
||||||
|
# Store the discovered IP address and MAC
|
||||||
|
self._discovered_host = discovery_info.ip
|
||||||
|
self._discovered_mac = discovery_info.macaddress
|
||||||
|
|
||||||
|
# Extract device_id from hostname (format: airobot-thermostat-t01xxxxxx)
|
||||||
|
hostname = discovery_info.hostname.lower()
|
||||||
|
device_id = hostname.replace("airobot-thermostat-", "").upper()
|
||||||
|
self._discovered_device_id = device_id
|
||||||
|
# Set unique_id to device_id for duplicate detection
|
||||||
|
await self.async_set_unique_id(device_id)
|
||||||
|
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
|
||||||
|
|
||||||
|
# Show the confirmation form
|
||||||
|
return await self.async_step_dhcp_confirm()
|
||||||
|
|
||||||
|
async def async_step_dhcp_confirm(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle DHCP discovery confirmation - ask for credentials only."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
# Combine discovered host and device_id with user-provided password
|
||||||
|
data = {
|
||||||
|
CONF_HOST: self._discovered_host,
|
||||||
|
CONF_USERNAME: self._discovered_device_id,
|
||||||
|
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, data)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
# Store MAC address in config entry data
|
||||||
|
if self._discovered_mac:
|
||||||
|
data[CONF_MAC] = self._discovered_mac
|
||||||
|
|
||||||
|
return self.async_create_entry(title=info.title, data=data)
|
||||||
|
|
||||||
|
# Only ask for password since we already have the device_id from discovery
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="dhcp_confirm",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
description_placeholders={
|
||||||
|
"host": self._discovered_host or "",
|
||||||
|
"device_id": self._discovered_device_id or "",
|
||||||
|
},
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
info = await validate_input(self.hass, user_input)
|
||||||
|
except CannotConnect:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidAuth:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
# Use device ID as unique ID to prevent duplicates
|
||||||
|
await self.async_set_unique_id(info.device_id)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(title=info.title, data=user_input)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CannotConnect(HomeAssistantError):
|
||||||
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(HomeAssistantError):
|
||||||
|
"""Error to indicate there is invalid auth."""
|
||||||
5
homeassistant/components/airobot/const.py
Normal file
5
homeassistant/components/airobot/const.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""Constants for the Airobot integration."""
|
||||||
|
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
DOMAIN: Final = "airobot"
|
||||||
59
homeassistant/components/airobot/coordinator.py
Normal file
59
homeassistant/components/airobot/coordinator.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""Coordinator for the Airobot integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from pyairobotrest import AirobotClient
|
||||||
|
from pyairobotrest.exceptions import AirobotAuthError, AirobotConnectionError
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .models import AirobotData
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Update interval - thermostat measures air every 30 seconds
|
||||||
|
UPDATE_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
|
type AirobotConfigEntry = ConfigEntry[AirobotDataUpdateCoordinator]
|
||||||
|
|
||||||
|
|
||||||
|
class AirobotDataUpdateCoordinator(DataUpdateCoordinator[AirobotData]):
|
||||||
|
"""Class to manage fetching Airobot data."""
|
||||||
|
|
||||||
|
config_entry: AirobotConfigEntry
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, entry: AirobotConfigEntry) -> None:
|
||||||
|
"""Initialize the coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=DOMAIN,
|
||||||
|
update_interval=UPDATE_INTERVAL,
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
session = async_get_clientsession(hass)
|
||||||
|
|
||||||
|
self.client = AirobotClient(
|
||||||
|
host=entry.data[CONF_HOST],
|
||||||
|
username=entry.data[CONF_USERNAME],
|
||||||
|
password=entry.data[CONF_PASSWORD],
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> AirobotData:
|
||||||
|
"""Fetch data from API endpoint."""
|
||||||
|
try:
|
||||||
|
status = await self.client.get_statuses()
|
||||||
|
settings = await self.client.get_settings()
|
||||||
|
except (AirobotAuthError, AirobotConnectionError) as err:
|
||||||
|
raise UpdateFailed(f"Failed to communicate with device: {err}") from err
|
||||||
|
|
||||||
|
return AirobotData(status=status, settings=settings)
|
||||||
42
homeassistant/components/airobot/entity.py
Normal file
42
homeassistant/components/airobot/entity.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""Base entity for Airobot integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_MAC
|
||||||
|
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||||
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import AirobotDataUpdateCoordinator
|
||||||
|
|
||||||
|
|
||||||
|
class AirobotEntity(CoordinatorEntity[AirobotDataUpdateCoordinator]):
|
||||||
|
"""Base class for Airobot entities."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AirobotDataUpdateCoordinator,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the entity."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
status = coordinator.data.status
|
||||||
|
settings = coordinator.data.settings
|
||||||
|
|
||||||
|
self._attr_unique_id = status.device_id
|
||||||
|
|
||||||
|
connections = set()
|
||||||
|
if (mac := coordinator.config_entry.data.get(CONF_MAC)) is not None:
|
||||||
|
connections.add((CONNECTION_NETWORK_MAC, mac))
|
||||||
|
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, status.device_id)},
|
||||||
|
connections=connections,
|
||||||
|
name=settings.device_name or status.device_id,
|
||||||
|
manufacturer="Airobot",
|
||||||
|
model="Thermostat",
|
||||||
|
model_id="TE1",
|
||||||
|
sw_version=str(status.fw_version),
|
||||||
|
hw_version=str(status.hw_version),
|
||||||
|
)
|
||||||
17
homeassistant/components/airobot/manifest.json
Normal file
17
homeassistant/components/airobot/manifest.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"domain": "airobot",
|
||||||
|
"name": "Airobot",
|
||||||
|
"codeowners": ["@mettolen"],
|
||||||
|
"config_flow": true,
|
||||||
|
"dhcp": [
|
||||||
|
{
|
||||||
|
"hostname": "airobot-thermostat-*"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/airobot",
|
||||||
|
"integration_type": "device",
|
||||||
|
"iot_class": "local_polling",
|
||||||
|
"loggers": ["pyairobotrest"],
|
||||||
|
"quality_scale": "bronze",
|
||||||
|
"requirements": ["pyairobotrest==0.1.0"]
|
||||||
|
}
|
||||||
15
homeassistant/components/airobot/models.py
Normal file
15
homeassistant/components/airobot/models.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""Models for the Airobot integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from pyairobotrest.models import ThermostatSettings, ThermostatStatus
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AirobotData:
|
||||||
|
"""Data from the Airobot coordinator."""
|
||||||
|
|
||||||
|
status: ThermostatStatus
|
||||||
|
settings: ThermostatSettings
|
||||||
70
homeassistant/components/airobot/quality_scale.yaml
Normal file
70
homeassistant/components/airobot/quality_scale.yaml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
rules:
|
||||||
|
# Bronze
|
||||||
|
action-setup:
|
||||||
|
status: exempt
|
||||||
|
comment: Integration does not register custom actions.
|
||||||
|
appropriate-polling: done
|
||||||
|
brands: done
|
||||||
|
common-modules: done
|
||||||
|
config-flow-test-coverage: done
|
||||||
|
config-flow: done
|
||||||
|
dependency-transparency: done
|
||||||
|
docs-actions:
|
||||||
|
status: exempt
|
||||||
|
comment: Integration does not register custom actions.
|
||||||
|
docs-high-level-description: done
|
||||||
|
docs-installation-instructions: done
|
||||||
|
docs-removal-instructions: done
|
||||||
|
entity-event-setup:
|
||||||
|
status: exempt
|
||||||
|
comment: Integration does not use event subscriptions.
|
||||||
|
entity-unique-id: done
|
||||||
|
has-entity-name: done
|
||||||
|
runtime-data: done
|
||||||
|
test-before-configure: done
|
||||||
|
test-before-setup: done
|
||||||
|
unique-config-entry: done
|
||||||
|
|
||||||
|
# Silver
|
||||||
|
action-exceptions: done
|
||||||
|
config-entry-unloading: done
|
||||||
|
docs-configuration-parameters: done
|
||||||
|
docs-installation-parameters: done
|
||||||
|
entity-unavailable: done
|
||||||
|
integration-owner: done
|
||||||
|
log-when-unavailable: done
|
||||||
|
parallel-updates: done
|
||||||
|
reauthentication-flow: todo
|
||||||
|
test-coverage: done
|
||||||
|
|
||||||
|
# Gold
|
||||||
|
devices: done
|
||||||
|
diagnostics: todo
|
||||||
|
discovery-update-info: done
|
||||||
|
discovery: done
|
||||||
|
docs-data-update: done
|
||||||
|
docs-examples: todo
|
||||||
|
docs-known-limitations: todo
|
||||||
|
docs-supported-devices: done
|
||||||
|
docs-supported-functions: done
|
||||||
|
docs-troubleshooting: done
|
||||||
|
docs-use-cases: todo
|
||||||
|
dynamic-devices:
|
||||||
|
status: exempt
|
||||||
|
comment: Single device integration, no dynamic device discovery needed.
|
||||||
|
entity-category: done
|
||||||
|
entity-device-class: done
|
||||||
|
entity-disabled-by-default: todo
|
||||||
|
entity-translations: todo
|
||||||
|
exception-translations: done
|
||||||
|
icon-translations: todo
|
||||||
|
reconfiguration-flow: todo
|
||||||
|
repair-issues: todo
|
||||||
|
stale-devices:
|
||||||
|
status: exempt
|
||||||
|
comment: Single device integration, no stale device handling needed.
|
||||||
|
|
||||||
|
# Platinum
|
||||||
|
async-dependency: done
|
||||||
|
inject-websession: done
|
||||||
|
strict-typing: todo
|
||||||
44
homeassistant/components/airobot/strings.json
Normal file
44
homeassistant/components/airobot/strings.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"dhcp_confirm": {
|
||||||
|
"data": {
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"password": "The thermostat password."
|
||||||
|
},
|
||||||
|
"description": "Airobot thermostat {device_id} discovered at {host}. Enter the password to complete setup. Find the password in the thermostat settings menu under Connectivity → Mobile app."
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
|
"username": "[%key:common::config_flow::data::username%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"host": "The hostname or IP address of your Airobot thermostat.",
|
||||||
|
"password": "The thermostat password.",
|
||||||
|
"username": "The thermostat Device ID (e.g., T01XXXXXX)."
|
||||||
|
},
|
||||||
|
"description": "Enter your Airobot thermostat connection details. Find the Device ID and password in the thermostat settings menu under Connectivity → Mobile app."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"set_preset_mode_failed": {
|
||||||
|
"message": "Failed to set preset mode to {preset_mode}."
|
||||||
|
},
|
||||||
|
"set_temperature_failed": {
|
||||||
|
"message": "Failed to set temperature to {temperature}."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,5 +7,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["airos==0.5.6"]
|
"requirements": ["airos==0.6.0"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
|
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||||
|
"unique_id_mismatch": "Re-authentication should be used for the same device not a new one"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"key_data_missing": "Expected data not returned from the device, check the documentation for supported devices",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
"flow_title": "Ubiquiti airOS device",
|
"flow_title": "Ubiquiti airOS device",
|
||||||
"step": {
|
"step": {
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
@@ -19,7 +31,6 @@
|
|||||||
},
|
},
|
||||||
"sections": {
|
"sections": {
|
||||||
"advanced_settings": {
|
"advanced_settings": {
|
||||||
"name": "[%key:component::airos::config::step::user::sections::advanced_settings::name%]",
|
|
||||||
"data": {
|
"data": {
|
||||||
"ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data::ssl%]",
|
"ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data::ssl%]",
|
||||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||||
@@ -27,24 +38,24 @@
|
|||||||
"data_description": {
|
"data_description": {
|
||||||
"ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data_description::ssl%]",
|
"ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data_description::ssl%]",
|
||||||
"verify_ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data_description::verify_ssl%]"
|
"verify_ssl": "[%key:component::airos::config::step::user::sections::advanced_settings::data_description::verify_ssl%]"
|
||||||
}
|
},
|
||||||
|
"name": "[%key:component::airos::config::step::user::sections::advanced_settings::name%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"username": "[%key:common::config_flow::data::username%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"host": "IP address or hostname of the airOS device",
|
"host": "IP address or hostname of the airOS device",
|
||||||
"username": "Administrator username for the airOS device, normally 'ubnt'",
|
"password": "Password configured through the UISP app or web interface",
|
||||||
"password": "Password configured through the UISP app or web interface"
|
"username": "Administrator username for the airOS device, normally 'ubnt'"
|
||||||
},
|
},
|
||||||
"sections": {
|
"sections": {
|
||||||
"advanced_settings": {
|
"advanced_settings": {
|
||||||
"name": "Advanced settings",
|
|
||||||
"data": {
|
"data": {
|
||||||
"ssl": "Use HTTPS",
|
"ssl": "Use HTTPS",
|
||||||
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
|
||||||
@@ -52,28 +63,17 @@
|
|||||||
"data_description": {
|
"data_description": {
|
||||||
"ssl": "Whether the connection should be encrypted (required for most devices)",
|
"ssl": "Whether the connection should be encrypted (required for most devices)",
|
||||||
"verify_ssl": "Whether the certificate should be verified when using HTTPS. This should be off for self-signed certificates"
|
"verify_ssl": "Whether the certificate should be verified when using HTTPS. This should be off for self-signed certificates"
|
||||||
}
|
},
|
||||||
|
"name": "Advanced settings"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
|
||||||
"key_data_missing": "Expected data not returned from the device, check the documentation for supported devices",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
|
||||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
|
||||||
"unique_id_mismatch": "Re-authentication should be used for the same device not a new one"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"binary_sensor": {
|
"binary_sensor": {
|
||||||
"port_forwarding": {
|
"dhcp6_server": {
|
||||||
"name": "Port forwarding"
|
"name": "DHCPv6 server"
|
||||||
},
|
},
|
||||||
"dhcp_client": {
|
"dhcp_client": {
|
||||||
"name": "DHCP client"
|
"name": "DHCP client"
|
||||||
@@ -81,8 +81,8 @@
|
|||||||
"dhcp_server": {
|
"dhcp_server": {
|
||||||
"name": "DHCP server"
|
"name": "DHCP server"
|
||||||
},
|
},
|
||||||
"dhcp6_server": {
|
"port_forwarding": {
|
||||||
"name": "DHCPv6 server"
|
"name": "Port forwarding"
|
||||||
},
|
},
|
||||||
"pppoe": {
|
"pppoe": {
|
||||||
"name": "PPPoE link"
|
"name": "PPPoE link"
|
||||||
@@ -99,20 +99,27 @@
|
|||||||
"router": "Router"
|
"router": "Router"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wireless_frequency": {
|
"host_uptime": {
|
||||||
"name": "Wireless frequency"
|
"name": "Uptime"
|
||||||
},
|
|
||||||
"wireless_essid": {
|
|
||||||
"name": "Wireless SSID"
|
|
||||||
},
|
},
|
||||||
"wireless_antenna_gain": {
|
"wireless_antenna_gain": {
|
||||||
"name": "Antenna gain"
|
"name": "Antenna gain"
|
||||||
},
|
},
|
||||||
"wireless_throughput_tx": {
|
"wireless_distance": {
|
||||||
"name": "Throughput transmit (actual)"
|
"name": "Wireless distance"
|
||||||
},
|
},
|
||||||
"wireless_throughput_rx": {
|
"wireless_essid": {
|
||||||
"name": "Throughput receive (actual)"
|
"name": "Wireless SSID"
|
||||||
|
},
|
||||||
|
"wireless_frequency": {
|
||||||
|
"name": "Wireless frequency"
|
||||||
|
},
|
||||||
|
"wireless_mode": {
|
||||||
|
"name": "Wireless mode",
|
||||||
|
"state": {
|
||||||
|
"point_to_multipoint": "Point-to-multipoint",
|
||||||
|
"point_to_point": "Point-to-point"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"wireless_polling_dl_capacity": {
|
"wireless_polling_dl_capacity": {
|
||||||
"name": "Download capacity"
|
"name": "Download capacity"
|
||||||
@@ -123,12 +130,6 @@
|
|||||||
"wireless_remote_hostname": {
|
"wireless_remote_hostname": {
|
||||||
"name": "Remote hostname"
|
"name": "Remote hostname"
|
||||||
},
|
},
|
||||||
"host_uptime": {
|
|
||||||
"name": "Uptime"
|
|
||||||
},
|
|
||||||
"wireless_distance": {
|
|
||||||
"name": "Wireless distance"
|
|
||||||
},
|
|
||||||
"wireless_role": {
|
"wireless_role": {
|
||||||
"name": "Wireless role",
|
"name": "Wireless role",
|
||||||
"state": {
|
"state": {
|
||||||
@@ -136,27 +137,26 @@
|
|||||||
"station": "Station"
|
"station": "Station"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wireless_mode": {
|
"wireless_throughput_rx": {
|
||||||
"name": "Wireless mode",
|
"name": "Throughput receive (actual)"
|
||||||
"state": {
|
},
|
||||||
"point_to_point": "Point-to-point",
|
"wireless_throughput_tx": {
|
||||||
"point_to_multipoint": "Point-to-multipoint"
|
"name": "Throughput transmit (actual)"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
"invalid_auth": {
|
|
||||||
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
|
||||||
},
|
|
||||||
"cannot_connect": {
|
"cannot_connect": {
|
||||||
"message": "[%key:common::config_flow::error::cannot_connect%]"
|
"message": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
},
|
},
|
||||||
"key_data_missing": {
|
|
||||||
"message": "Key data not returned from device"
|
|
||||||
},
|
|
||||||
"error_data_missing": {
|
"error_data_missing": {
|
||||||
"message": "Data incomplete or missing"
|
"message": "Data incomplete or missing"
|
||||||
|
},
|
||||||
|
"invalid_auth": {
|
||||||
|
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
||||||
|
},
|
||||||
|
"key_data_missing": {
|
||||||
|
"message": "Key data not returned from device"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,21 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
"title": "Identify the device",
|
|
||||||
"description": "Provide the IP address or mDNS of the device and its password",
|
|
||||||
"data": {
|
|
||||||
"ip_address": "[%key:common::config_flow::data::ip%]",
|
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
"invalid_input": "[%key:common::config_flow::error::invalid_host%]"
|
"invalid_input": "[%key:common::config_flow::error::invalid_host%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"step": {
|
"step": {
|
||||||
"init": {
|
"user": {
|
||||||
"title": "Configure air-Q integration",
|
|
||||||
"data": {
|
"data": {
|
||||||
"return_average": "Show values averaged by the device",
|
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||||
"clip_negatives": "Clip negative values"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"description": "Provide the IP address or mDNS of the device and its password",
|
||||||
"return_average": "air-Q allows to poll both the noisy sensor readings as well as the values averaged on the device (default)",
|
"title": "Identify the device"
|
||||||
"clip_negatives": "For baseline calibration purposes, certain sensor values may briefly become negative. The default behavior is to clip such values to 0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -53,8 +38,11 @@
|
|||||||
"bromine": {
|
"bromine": {
|
||||||
"name": "Bromine"
|
"name": "Bromine"
|
||||||
},
|
},
|
||||||
"methanethiol": {
|
"carbon_disulfide": {
|
||||||
"name": "Methanethiol"
|
"name": "Carbon disulfide"
|
||||||
|
},
|
||||||
|
"carbon_monoxide": {
|
||||||
|
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
||||||
},
|
},
|
||||||
"chlorine": {
|
"chlorine": {
|
||||||
"name": "Chlorine"
|
"name": "Chlorine"
|
||||||
@@ -62,12 +50,6 @@
|
|||||||
"chlorine_dioxide": {
|
"chlorine_dioxide": {
|
||||||
"name": "Chlorine dioxide"
|
"name": "Chlorine dioxide"
|
||||||
},
|
},
|
||||||
"carbon_disulfide": {
|
|
||||||
"name": "Carbon disulfide"
|
|
||||||
},
|
|
||||||
"carbon_monoxide": {
|
|
||||||
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
|
|
||||||
},
|
|
||||||
"dew_point": {
|
"dew_point": {
|
||||||
"name": "Dew point"
|
"name": "Dew point"
|
||||||
},
|
},
|
||||||
@@ -77,36 +59,51 @@
|
|||||||
"ethylene": {
|
"ethylene": {
|
||||||
"name": "Ethylene"
|
"name": "Ethylene"
|
||||||
},
|
},
|
||||||
"formaldehyde": {
|
|
||||||
"name": "Formaldehyde"
|
|
||||||
},
|
|
||||||
"fluorine": {
|
"fluorine": {
|
||||||
"name": "Fluorine"
|
"name": "Fluorine"
|
||||||
},
|
},
|
||||||
"hydrogen_sulfide": {
|
"formaldehyde": {
|
||||||
"name": "Hydrogen sulfide"
|
"name": "Formaldehyde"
|
||||||
|
},
|
||||||
|
"health_index": {
|
||||||
|
"name": "Health index"
|
||||||
},
|
},
|
||||||
"hydrochloric_acid": {
|
"hydrochloric_acid": {
|
||||||
"name": "Hydrochloric acid"
|
"name": "Hydrochloric acid"
|
||||||
},
|
},
|
||||||
|
"hydrogen": {
|
||||||
|
"name": "Hydrogen"
|
||||||
|
},
|
||||||
"hydrogen_cyanide": {
|
"hydrogen_cyanide": {
|
||||||
"name": "Hydrogen cyanide"
|
"name": "Hydrogen cyanide"
|
||||||
},
|
},
|
||||||
"hydrogen_fluoride": {
|
"hydrogen_fluoride": {
|
||||||
"name": "Hydrogen fluoride"
|
"name": "Hydrogen fluoride"
|
||||||
},
|
},
|
||||||
"health_index": {
|
|
||||||
"name": "Health index"
|
|
||||||
},
|
|
||||||
"hydrogen": {
|
|
||||||
"name": "Hydrogen"
|
|
||||||
},
|
|
||||||
"hydrogen_peroxide": {
|
"hydrogen_peroxide": {
|
||||||
"name": "Hydrogen peroxide"
|
"name": "Hydrogen peroxide"
|
||||||
},
|
},
|
||||||
|
"hydrogen_phosphide": {
|
||||||
|
"name": "Hydrogen phosphide"
|
||||||
|
},
|
||||||
|
"hydrogen_sulfide": {
|
||||||
|
"name": "Hydrogen sulfide"
|
||||||
|
},
|
||||||
|
"industrial_volatile_organic_compounds": {
|
||||||
|
"name": "VOCs (industrial)"
|
||||||
|
},
|
||||||
|
"maximum_noise": {
|
||||||
|
"name": "Noise (maximum)"
|
||||||
|
},
|
||||||
"methane": {
|
"methane": {
|
||||||
"name": "Methane"
|
"name": "Methane"
|
||||||
},
|
},
|
||||||
|
"methanethiol": {
|
||||||
|
"name": "Methanethiol"
|
||||||
|
},
|
||||||
|
"noise": {
|
||||||
|
"name": "Noise"
|
||||||
|
},
|
||||||
"organic_acid": {
|
"organic_acid": {
|
||||||
"name": "Organic acid"
|
"name": "Organic acid"
|
||||||
},
|
},
|
||||||
@@ -116,36 +113,39 @@
|
|||||||
"performance_index": {
|
"performance_index": {
|
||||||
"name": "Performance index"
|
"name": "Performance index"
|
||||||
},
|
},
|
||||||
"hydrogen_phosphide": {
|
|
||||||
"name": "Hydrogen phosphide"
|
|
||||||
},
|
|
||||||
"relative_pressure": {
|
|
||||||
"name": "Relative pressure"
|
|
||||||
},
|
|
||||||
"propane": {
|
"propane": {
|
||||||
"name": "Propane"
|
"name": "Propane"
|
||||||
},
|
},
|
||||||
"refigerant": {
|
|
||||||
"name": "Refrigerant"
|
|
||||||
},
|
|
||||||
"silicon_hydride": {
|
|
||||||
"name": "Silicon hydride"
|
|
||||||
},
|
|
||||||
"noise": {
|
|
||||||
"name": "Noise"
|
|
||||||
},
|
|
||||||
"maximum_noise": {
|
|
||||||
"name": "Noise (maximum)"
|
|
||||||
},
|
|
||||||
"radon": {
|
"radon": {
|
||||||
"name": "Radon"
|
"name": "Radon"
|
||||||
},
|
},
|
||||||
"industrial_volatile_organic_compounds": {
|
"refigerant": {
|
||||||
"name": "VOCs (industrial)"
|
"name": "Refrigerant"
|
||||||
|
},
|
||||||
|
"relative_pressure": {
|
||||||
|
"name": "Relative pressure"
|
||||||
|
},
|
||||||
|
"silicon_hydride": {
|
||||||
|
"name": "Silicon hydride"
|
||||||
},
|
},
|
||||||
"virus_index": {
|
"virus_index": {
|
||||||
"name": "Virus index"
|
"name": "Virus index"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"clip_negatives": "Clip negative values",
|
||||||
|
"return_average": "Show values averaged by the device"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"clip_negatives": "For baseline calibration purposes, certain sensor values may briefly become negative. The default behavior is to clip such values to 0",
|
||||||
|
"return_average": "air-Q allows to poll both the noisy sensor readings as well as the values averaged on the device (default)"
|
||||||
|
},
|
||||||
|
"title": "Configure air-Q integration"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
@@ -8,29 +16,21 @@
|
|||||||
},
|
},
|
||||||
"description": "Log in at {url} to find your credentials"
|
"description": "Log in at {url} to find your credentials"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"radon": {
|
|
||||||
"name": "Radon"
|
|
||||||
},
|
|
||||||
"light": {
|
"light": {
|
||||||
"name": "Light"
|
"name": "Light"
|
||||||
},
|
},
|
||||||
"virus_risk": {
|
|
||||||
"name": "Virus Risk"
|
|
||||||
},
|
|
||||||
"mold": {
|
"mold": {
|
||||||
"name": "Mold"
|
"name": "Mold"
|
||||||
|
},
|
||||||
|
"radon": {
|
||||||
|
"name": "Radon"
|
||||||
|
},
|
||||||
|
"virus_risk": {
|
||||||
|
"name": "Virus Risk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from homeassistant.components.bluetooth import (
|
|||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_ADDRESS
|
from homeassistant.const import CONF_ADDRESS
|
||||||
|
|
||||||
from .const import DOMAIN, MFCT_ID
|
from .const import DEVICE_MODEL, DOMAIN, MFCT_ID
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -128,15 +128,15 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Confirm discovery."""
|
"""Confirm discovery."""
|
||||||
|
assert self._discovered_device is not None
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
if (
|
if self._discovered_device.device.firmware.need_firmware_upgrade:
|
||||||
self._discovered_device is not None
|
|
||||||
and self._discovered_device.device.firmware.need_firmware_upgrade
|
|
||||||
):
|
|
||||||
return self.async_abort(reason="firmware_upgrade_required")
|
return self.async_abort(reason="firmware_upgrade_required")
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self.context["title_placeholders"]["name"], data={}
|
title=self.context["title_placeholders"]["name"],
|
||||||
|
data={DEVICE_MODEL: self._discovered_device.device.model.value},
|
||||||
)
|
)
|
||||||
|
|
||||||
self._set_confirm_only()
|
self._set_confirm_only()
|
||||||
@@ -164,7 +164,10 @@ class AirthingsConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
self._discovered_device = discovery
|
self._discovered_device = discovery
|
||||||
|
|
||||||
return self.async_create_entry(title=discovery.name, data={})
|
return self.async_create_entry(
|
||||||
|
title=discovery.name,
|
||||||
|
data={DEVICE_MODEL: discovery.device.model.value},
|
||||||
|
)
|
||||||
|
|
||||||
current_addresses = self._async_current_ids(include_ignore=False)
|
current_addresses = self._async_current_ids(include_ignore=False)
|
||||||
devices: list[BluetoothServiceInfoBleak] = []
|
devices: list[BluetoothServiceInfoBleak] = []
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
"""Constants for Airthings BLE."""
|
"""Constants for Airthings BLE."""
|
||||||
|
|
||||||
|
from airthings_ble import AirthingsDeviceType
|
||||||
|
|
||||||
DOMAIN = "airthings_ble"
|
DOMAIN = "airthings_ble"
|
||||||
MFCT_ID = 820
|
MFCT_ID = 820
|
||||||
|
|
||||||
VOLUME_BECQUEREL = "Bq/m³"
|
VOLUME_BECQUEREL = "Bq/m³"
|
||||||
VOLUME_PICOCURIE = "pCi/L"
|
VOLUME_PICOCURIE = "pCi/L"
|
||||||
|
|
||||||
|
DEVICE_MODEL = "device_model"
|
||||||
|
|
||||||
DEFAULT_SCAN_INTERVAL = 300
|
DEFAULT_SCAN_INTERVAL = 300
|
||||||
|
DEVICE_SPECIFIC_SCAN_INTERVAL = {AirthingsDeviceType.CORENTIUM_HOME_2.value: 1800}
|
||||||
|
|
||||||
MAX_RETRIES_AFTER_STARTUP = 5
|
MAX_RETRIES_AFTER_STARTUP = 5
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
|||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
|
|
||||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
|
from .const import (
|
||||||
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
DEVICE_MODEL,
|
||||||
|
DEVICE_SPECIFIC_SCAN_INTERVAL,
|
||||||
|
DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -34,12 +39,18 @@ class AirthingsBLEDataUpdateCoordinator(DataUpdateCoordinator[AirthingsDevice]):
|
|||||||
self.airthings = AirthingsBluetoothDeviceData(
|
self.airthings = AirthingsBluetoothDeviceData(
|
||||||
_LOGGER, hass.config.units is METRIC_SYSTEM
|
_LOGGER, hass.config.units is METRIC_SYSTEM
|
||||||
)
|
)
|
||||||
|
|
||||||
|
device_model = entry.data.get(DEVICE_MODEL)
|
||||||
|
interval = DEVICE_SPECIFIC_SCAN_INTERVAL.get(
|
||||||
|
device_model, DEFAULT_SCAN_INTERVAL
|
||||||
|
)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
config_entry=entry,
|
config_entry=entry,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
|
update_interval=timedelta(seconds=interval),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _async_setup(self) -> None:
|
async def _async_setup(self) -> None:
|
||||||
@@ -58,11 +69,29 @@ class AirthingsBLEDataUpdateCoordinator(DataUpdateCoordinator[AirthingsDevice]):
|
|||||||
)
|
)
|
||||||
self.ble_device = ble_device
|
self.ble_device = ble_device
|
||||||
|
|
||||||
|
if DEVICE_MODEL not in self.config_entry.data:
|
||||||
|
_LOGGER.debug("Fetching device info for migration")
|
||||||
|
try:
|
||||||
|
data = await self.airthings.update_device(self.ble_device)
|
||||||
|
except Exception as err:
|
||||||
|
raise UpdateFailed(
|
||||||
|
f"Unable to fetch data for migration: {err}"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
self.hass.config_entries.async_update_entry(
|
||||||
|
self.config_entry,
|
||||||
|
data={**self.config_entry.data, DEVICE_MODEL: data.model.value},
|
||||||
|
)
|
||||||
|
self.update_interval = timedelta(
|
||||||
|
seconds=DEVICE_SPECIFIC_SCAN_INTERVAL.get(
|
||||||
|
data.model.value, DEFAULT_SCAN_INTERVAL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_update_data(self) -> AirthingsDevice:
|
async def _async_update_data(self) -> AirthingsDevice:
|
||||||
"""Get data from Airthings BLE."""
|
"""Get data from Airthings BLE."""
|
||||||
try:
|
try:
|
||||||
data = await self.airthings.update_device(self.ble_device)
|
data = await self.airthings.update_device(self.ble_device)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise UpdateFailed(f"Unable to fetch data: {err}") from err
|
raise UpdateFailed(f"Unable to fetch data: {err}") from err
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
"radon_1day_avg": {
|
"radon_1day_avg": {
|
||||||
"default": "mdi:radioactive"
|
"default": "mdi:radioactive"
|
||||||
},
|
},
|
||||||
"radon_longterm_avg": {
|
"radon_1day_level": {
|
||||||
"default": "mdi:radioactive"
|
"default": "mdi:radioactive"
|
||||||
},
|
},
|
||||||
"radon_1day_level": {
|
"radon_longterm_avg": {
|
||||||
"default": "mdi:radioactive"
|
"default": "mdi:radioactive"
|
||||||
},
|
},
|
||||||
"radon_longterm_level": {
|
"radon_longterm_level": {
|
||||||
|
|||||||
@@ -28,5 +28,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["airthings-ble==1.1.1"]
|
"requirements": ["airthings-ble==1.2.0"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,49 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"firmware_upgrade_required": "Your device requires a firmware upgrade. Please use the Airthings app (Android/iOS) to upgrade it.",
|
||||||
|
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
|
"unsupported_device": "Unsupported device"
|
||||||
|
},
|
||||||
"flow_title": "{name}",
|
"flow_title": "{name}",
|
||||||
"step": {
|
"step": {
|
||||||
|
"bluetooth_confirm": {
|
||||||
|
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "[%key:component::bluetooth::config::step::user::description%]",
|
|
||||||
"data": {
|
"data": {
|
||||||
"address": "[%key:common::config_flow::data::device%]"
|
"address": "[%key:common::config_flow::data::device%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"address": "The Airthings devices discovered via Bluetooth."
|
"address": "The Airthings devices discovered via Bluetooth."
|
||||||
}
|
},
|
||||||
},
|
"description": "[%key:component::bluetooth::config::step::user::description%]"
|
||||||
"bluetooth_confirm": {
|
|
||||||
"description": "[%key:component::bluetooth::config::step::bluetooth_confirm::description%]"
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"firmware_upgrade_required": "Your device requires a firmware upgrade. Please use the Airthings app (Android/iOS) to upgrade it.",
|
|
||||||
"unsupported_device": "Unsupported device",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"radon_1day_avg": {
|
"ambient_noise": {
|
||||||
"name": "Radon 1-day average"
|
"name": "Ambient noise"
|
||||||
},
|
|
||||||
"radon_longterm_avg": {
|
|
||||||
"name": "Radon longterm average"
|
|
||||||
},
|
|
||||||
"radon_1day_level": {
|
|
||||||
"name": "Radon 1-day level"
|
|
||||||
},
|
|
||||||
"radon_longterm_level": {
|
|
||||||
"name": "Radon longterm level"
|
|
||||||
},
|
},
|
||||||
"illuminance": {
|
"illuminance": {
|
||||||
"name": "[%key:component::sensor::entity_component::illuminance::name%]"
|
"name": "[%key:component::sensor::entity_component::illuminance::name%]"
|
||||||
},
|
},
|
||||||
"ambient_noise": {
|
"radon_1day_avg": {
|
||||||
"name": "Ambient noise"
|
"name": "Radon 1-day average"
|
||||||
|
},
|
||||||
|
"radon_1day_level": {
|
||||||
|
"name": "Radon 1-day level"
|
||||||
|
},
|
||||||
|
"radon_longterm_avg": {
|
||||||
|
"name": "Radon longterm average"
|
||||||
|
},
|
||||||
|
"radon_longterm_level": {
|
||||||
|
"name": "Radon longterm level"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Set up your AirTouch 4 connection details.",
|
|
||||||
"data": {
|
"data": {
|
||||||
"host": "[%key:common::config_flow::data::host%]"
|
"host": "[%key:common::config_flow::data::host%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"host": "The hostname or IP address of your AirTouch controller."
|
"host": "The hostname or IP address of your AirTouch controller."
|
||||||
}
|
},
|
||||||
|
"title": "Set up your AirTouch 4 connection details."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"host": "[%key:common::config_flow::data::host%]"
|
"host": "[%key:common::config_flow::data::host%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
"state_attributes": {
|
"state_attributes": {
|
||||||
"fan_mode": {
|
"fan_mode": {
|
||||||
"state": {
|
"state": {
|
||||||
"turbo": "Turbo",
|
"intelligent_auto": "Intelligent Auto",
|
||||||
"intelligent_auto": "Intelligent Auto"
|
"turbo": "Turbo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"pollutant_level": {
|
|
||||||
"default": "mdi:gauge"
|
|
||||||
},
|
|
||||||
"pollutant_label": {
|
"pollutant_label": {
|
||||||
"default": "mdi:chemical-weapon"
|
"default": "mdi:chemical-weapon"
|
||||||
|
},
|
||||||
|
"pollutant_level": {
|
||||||
|
"default": "mdi:gauge"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,44 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
|
||||||
|
"location_not_found": "Location not found",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"geography_by_coords": {
|
"geography_by_coords": {
|
||||||
"title": "Configure a geography",
|
|
||||||
"description": "Use the AirVisual cloud API to monitor a latitude/longitude.",
|
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"latitude": "[%key:common::config_flow::data::latitude%]",
|
"latitude": "[%key:common::config_flow::data::latitude%]",
|
||||||
"longitude": "[%key:common::config_flow::data::longitude%]"
|
"longitude": "[%key:common::config_flow::data::longitude%]"
|
||||||
}
|
},
|
||||||
|
"description": "Use the AirVisual cloud API to monitor a latitude/longitude.",
|
||||||
|
"title": "Configure a geography"
|
||||||
},
|
},
|
||||||
"geography_by_name": {
|
"geography_by_name": {
|
||||||
"title": "[%key:component::airvisual::config::step::geography_by_coords::title%]",
|
|
||||||
"description": "Use the AirVisual cloud API to monitor a city/state/country.",
|
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"state": "State",
|
"country": "[%key:common::config_flow::data::country%]",
|
||||||
"country": "[%key:common::config_flow::data::country%]"
|
"state": "State"
|
||||||
}
|
},
|
||||||
|
"description": "Use the AirVisual cloud API to monitor a city/state/country.",
|
||||||
|
"title": "[%key:component::airvisual::config::step::geography_by_coords::title%]"
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"title": "Re-authenticate AirVisual",
|
|
||||||
"data": {
|
"data": {
|
||||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||||
}
|
},
|
||||||
|
"title": "Re-authenticate AirVisual"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"title": "Configure AirVisual",
|
"description": "Pick what type of AirVisual data you want to monitor.",
|
||||||
"description": "Pick what type of AirVisual data you want to monitor."
|
"title": "Configure AirVisual"
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"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%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"options": {
|
|
||||||
"step": {
|
|
||||||
"init": {
|
|
||||||
"title": "[%key:component::airvisual::config::step::user::title%]",
|
|
||||||
"data": {
|
|
||||||
"show_on_map": "Show monitored geography on the map"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,19 +57,29 @@
|
|||||||
"pollutant_level": {
|
"pollutant_level": {
|
||||||
"state": {
|
"state": {
|
||||||
"good": "Good",
|
"good": "Good",
|
||||||
|
"hazardous": "Hazardous",
|
||||||
"moderate": "Moderate",
|
"moderate": "Moderate",
|
||||||
"unhealthy": "Unhealthy",
|
"unhealthy": "Unhealthy",
|
||||||
"unhealthy_sensitive": "Unhealthy for sensitive groups",
|
"unhealthy_sensitive": "Unhealthy for sensitive groups",
|
||||||
"very_unhealthy": "Very unhealthy",
|
"very_unhealthy": "Very unhealthy"
|
||||||
"hazardous": "Hazardous"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
"airvisual_pro_migration": {
|
"airvisual_pro_migration": {
|
||||||
"title": "{ip_address} is now part of the AirVisual Pro integration",
|
"description": "AirVisual Pro units are now their own Home Assistant integration (as opposed to be included with the original AirVisual integration that uses the AirVisual cloud API). The Pro device located at `{ip_address}` has automatically been migrated.\n\nAs part of that migration, the Pro's device ID has changed from `{old_device_id}` to `{new_device_id}`. Please update these automations to use the new device ID: {device_automations_string}.",
|
||||||
"description": "AirVisual Pro units are now their own Home Assistant integration (as opposed to be included with the original AirVisual integration that uses the AirVisual cloud API). The Pro device located at `{ip_address}` has automatically been migrated.\n\nAs part of that migration, the Pro's device ID has changed from `{old_device_id}` to `{new_device_id}`. Please update these automations to use the new device ID: {device_automations_string}."
|
"title": "{ip_address} is now part of the AirVisual Pro integration"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"show_on_map": "Show monitored geography on the map"
|
||||||
|
},
|
||||||
|
"title": "[%key:component::airvisual::config::step::user::title%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
"description": "[%key:component::airvisual_pro::config::step::user::description%]",
|
|
||||||
"data": {
|
"data": {
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
}
|
},
|
||||||
|
"description": "[%key:component::airvisual_pro::config::step::user::description%]"
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "The password can be retrieved from the AirVisual Pro's UI.",
|
|
||||||
"data": {
|
"data": {
|
||||||
"ip_address": "[%key:common::config_flow::data::host%]",
|
"ip_address": "[%key:common::config_flow::data::host%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"ip_address": "The hostname or IP address of your AirVisual Pro device."
|
"ip_address": "The hostname or IP address of your AirVisual Pro device."
|
||||||
}
|
},
|
||||||
|
"description": "The password can be retrieved from the AirVisual Pro's UI."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"pm01": {
|
|
||||||
"name": "PM0.1"
|
|
||||||
},
|
|
||||||
"outdoor_air_quality_index": {
|
"outdoor_air_quality_index": {
|
||||||
"name": "Outdoor air quality index"
|
"name": "Outdoor air quality index"
|
||||||
|
},
|
||||||
|
"pm01": {
|
||||||
|
"name": "PM0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,5 +11,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"],
|
"loggers": ["aioairzone"],
|
||||||
"requirements": ["aioairzone==1.0.1"]
|
"requirements": ["aioairzone==1.0.2"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"discovered_connection": {
|
"discovered_connection": {
|
||||||
"data": {
|
"data": {
|
||||||
"id": "[%key:component::airzone::config::step::user::data::id%]",
|
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"id": "[%key:component::airzone::config::step::user::data::id%]",
|
||||||
"port": "[%key:common::config_flow::data::port%]"
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"id": "System ID",
|
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"id": "System ID",
|
||||||
"port": "[%key:common::config_flow::data::port%]"
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,19 +37,19 @@
|
|||||||
"grille_angles": {
|
"grille_angles": {
|
||||||
"name": "Cold angle",
|
"name": "Cold angle",
|
||||||
"state": {
|
"state": {
|
||||||
"90deg": "90°",
|
"40deg": "40°",
|
||||||
"50deg": "50°",
|
|
||||||
"45deg": "45°",
|
"45deg": "45°",
|
||||||
"40deg": "40°"
|
"50deg": "50°",
|
||||||
|
"90deg": "90°"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"heat_angles": {
|
"heat_angles": {
|
||||||
"name": "Heat angle",
|
"name": "Heat angle",
|
||||||
"state": {
|
"state": {
|
||||||
"90deg": "[%key:component::airzone::entity::select::grille_angles::state::90deg%]",
|
"40deg": "[%key:component::airzone::entity::select::grille_angles::state::40deg%]",
|
||||||
"50deg": "[%key:component::airzone::entity::select::grille_angles::state::50deg%]",
|
|
||||||
"45deg": "[%key:component::airzone::entity::select::grille_angles::state::45deg%]",
|
"45deg": "[%key:component::airzone::entity::select::grille_angles::state::45deg%]",
|
||||||
"40deg": "[%key:component::airzone::entity::select::grille_angles::state::40deg%]"
|
"50deg": "[%key:component::airzone::entity::select::grille_angles::state::50deg%]",
|
||||||
|
"90deg": "[%key:component::airzone::entity::select::grille_angles::state::90deg%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"modes": {
|
"modes": {
|
||||||
@@ -66,20 +66,20 @@
|
|||||||
"q_adapt": {
|
"q_adapt": {
|
||||||
"name": "Q-Adapt",
|
"name": "Q-Adapt",
|
||||||
"state": {
|
"state": {
|
||||||
"standard": "Standard",
|
"maximum": "Maximum",
|
||||||
|
"minimum": "Minimum",
|
||||||
"power": "Power",
|
"power": "Power",
|
||||||
"silence": "Silence",
|
"silence": "Silence",
|
||||||
"minimum": "Minimum",
|
"standard": "Standard"
|
||||||
"maximum": "Maximum"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sleep_times": {
|
"sleep_times": {
|
||||||
"name": "Sleep",
|
"name": "Sleep",
|
||||||
"state": {
|
"state": {
|
||||||
"off": "[%key:common::state::off%]",
|
|
||||||
"30m": "30 minutes",
|
"30m": "30 minutes",
|
||||||
"60m": "60 minutes",
|
"60m": "60 minutes",
|
||||||
"90m": "90 minutes"
|
"90m": "90 minutes",
|
||||||
|
"off": "[%key:common::state::off%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"id": "Installation",
|
"id": "Installation",
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"username": "[%key:common::config_flow::data::username%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"air_quality": {
|
"air_quality": {
|
||||||
"name": "Air Quality mode",
|
"name": "Air Quality mode",
|
||||||
"state": {
|
"state": {
|
||||||
|
"auto": "[%key:common::state::auto%]",
|
||||||
"off": "[%key:common::state::off%]",
|
"off": "[%key:common::state::off%]",
|
||||||
"on": "[%key:common::state::on%]",
|
"on": "[%key:common::state::on%]"
|
||||||
"auto": "[%key:common::state::auto%]"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"modes": {
|
"modes": {
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
|
||||||
"pick_implementation": {
|
|
||||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
|
||||||
},
|
|
||||||
"reauth_confirm": {
|
|
||||||
"title": "[%key:common::config_flow::title::reauth%]",
|
|
||||||
"description": "Aladdin Connect needs to re-authenticate your account"
|
|
||||||
},
|
|
||||||
"oauth_discovery": {
|
|
||||||
"description": "Home Assistant has found an Aladdin Connect device on your network. Press **Submit** to continue setting up Aladdin Connect."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
|
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
||||||
|
"cloud_not_enabled": "Please make sure you run Home Assistant with `{default_config}` enabled in your configuration.yaml.",
|
||||||
|
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
||||||
|
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
||||||
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
|
||||||
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
|
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
|
||||||
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
|
||||||
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
|
||||||
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
|
|
||||||
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
|
|
||||||
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
|
|
||||||
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
|
||||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||||
"wrong_account": "You are authenticated with a different account than the one set up. Please authenticate with the configured account.",
|
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
|
||||||
"cloud_not_enabled": "Please make sure you run Home Assistant with `{default_config}` enabled in your configuration.yaml."
|
"wrong_account": "You are authenticated with a different account than the one set up. Please authenticate with the configured account."
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"create_entry": {
|
||||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"oauth_discovery": {
|
||||||
|
"description": "Home Assistant has found an Aladdin Connect device on your network. Press **Submit** to continue setting up Aladdin Connect."
|
||||||
|
},
|
||||||
|
"pick_implementation": {
|
||||||
|
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||||
|
},
|
||||||
|
"reauth_confirm": {
|
||||||
|
"description": "Aladdin Connect needs to re-authenticate your account",
|
||||||
|
"title": "[%key:common::config_flow::title::reauth%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,23 +18,23 @@
|
|||||||
"alarm_arm_away": {
|
"alarm_arm_away": {
|
||||||
"service": "mdi:shield-lock"
|
"service": "mdi:shield-lock"
|
||||||
},
|
},
|
||||||
|
"alarm_arm_custom_bypass": {
|
||||||
|
"service": "mdi:security"
|
||||||
|
},
|
||||||
"alarm_arm_home": {
|
"alarm_arm_home": {
|
||||||
"service": "mdi:shield-home"
|
"service": "mdi:shield-home"
|
||||||
},
|
},
|
||||||
"alarm_arm_night": {
|
"alarm_arm_night": {
|
||||||
"service": "mdi:shield-moon"
|
"service": "mdi:shield-moon"
|
||||||
},
|
},
|
||||||
"alarm_arm_custom_bypass": {
|
"alarm_arm_vacation": {
|
||||||
"service": "mdi:security"
|
"service": "mdi:shield-airplane"
|
||||||
},
|
},
|
||||||
"alarm_disarm": {
|
"alarm_disarm": {
|
||||||
"service": "mdi:shield-off"
|
"service": "mdi:shield-off"
|
||||||
},
|
},
|
||||||
"alarm_trigger": {
|
"alarm_trigger": {
|
||||||
"service": "mdi:bell-ring"
|
"service": "mdi:bell-ring"
|
||||||
},
|
|
||||||
"alarm_arm_vacation": {
|
|
||||||
"service": "mdi:shield-airplane"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "Alarm control panel",
|
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
"arm_away": "Arm {entity_name} away",
|
"arm_away": "Arm {entity_name} away",
|
||||||
@@ -10,24 +9,24 @@
|
|||||||
"trigger": "Trigger {entity_name}"
|
"trigger": "Trigger {entity_name}"
|
||||||
},
|
},
|
||||||
"condition_type": {
|
"condition_type": {
|
||||||
"is_triggered": "{entity_name} is triggered",
|
|
||||||
"is_disarmed": "{entity_name} is disarmed",
|
|
||||||
"is_armed_home": "{entity_name} is armed home",
|
|
||||||
"is_armed_away": "{entity_name} is armed away",
|
"is_armed_away": "{entity_name} is armed away",
|
||||||
|
"is_armed_home": "{entity_name} is armed home",
|
||||||
"is_armed_night": "{entity_name} is armed night",
|
"is_armed_night": "{entity_name} is armed night",
|
||||||
"is_armed_vacation": "{entity_name} is armed vacation"
|
"is_armed_vacation": "{entity_name} is armed vacation",
|
||||||
|
"is_disarmed": "{entity_name} is disarmed",
|
||||||
|
"is_triggered": "{entity_name} is triggered"
|
||||||
},
|
},
|
||||||
"extra_fields": {
|
"extra_fields": {
|
||||||
"code": "Code",
|
"code": "Code",
|
||||||
"for": "[%key:common::device_automation::extra_fields::for%]"
|
"for": "[%key:common::device_automation::extra_fields::for%]"
|
||||||
},
|
},
|
||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"triggered": "{entity_name} triggered",
|
|
||||||
"disarmed": "{entity_name} disarmed",
|
|
||||||
"armed_home": "{entity_name} armed home",
|
|
||||||
"armed_away": "{entity_name} armed away",
|
"armed_away": "{entity_name} armed away",
|
||||||
|
"armed_home": "{entity_name} armed home",
|
||||||
"armed_night": "{entity_name} armed night",
|
"armed_night": "{entity_name} armed night",
|
||||||
"armed_vacation": "{entity_name} armed vacation"
|
"armed_vacation": "{entity_name} armed vacation",
|
||||||
|
"disarmed": "{entity_name} disarmed",
|
||||||
|
"triggered": "{entity_name} triggered"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity_component": {
|
"entity_component": {
|
||||||
@@ -35,106 +34,34 @@
|
|||||||
"name": "[%key:component::alarm_control_panel::title%]",
|
"name": "[%key:component::alarm_control_panel::title%]",
|
||||||
"state": {
|
"state": {
|
||||||
"armed": "Armed",
|
"armed": "Armed",
|
||||||
"disarmed": "Disarmed",
|
|
||||||
"armed_home": "Armed home",
|
|
||||||
"armed_away": "Armed away",
|
"armed_away": "Armed away",
|
||||||
|
"armed_custom_bypass": "Armed custom bypass",
|
||||||
|
"armed_home": "Armed home",
|
||||||
"armed_night": "Armed night",
|
"armed_night": "Armed night",
|
||||||
"armed_vacation": "Armed vacation",
|
"armed_vacation": "Armed vacation",
|
||||||
"armed_custom_bypass": "Armed custom bypass",
|
|
||||||
"pending": "Pending",
|
|
||||||
"arming": "Arming",
|
"arming": "Arming",
|
||||||
|
"disarmed": "Disarmed",
|
||||||
"disarming": "Disarming",
|
"disarming": "Disarming",
|
||||||
|
"pending": "Pending",
|
||||||
"triggered": "Triggered"
|
"triggered": "Triggered"
|
||||||
},
|
},
|
||||||
"state_attributes": {
|
"state_attributes": {
|
||||||
"code_format": {
|
|
||||||
"name": "Code format",
|
|
||||||
"state": {
|
|
||||||
"text": "Text",
|
|
||||||
"number": "Number"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"changed_by": {
|
"changed_by": {
|
||||||
"name": "Changed by"
|
"name": "Changed by"
|
||||||
},
|
},
|
||||||
"code_arm_required": {
|
"code_arm_required": {
|
||||||
"name": "Code for arming",
|
"name": "Code for arming",
|
||||||
"state": {
|
"state": {
|
||||||
"true": "Required",
|
"false": "Not required",
|
||||||
"false": "Not required"
|
"true": "Required"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"code_format": {
|
||||||
|
"name": "Code format",
|
||||||
|
"state": {
|
||||||
|
"number": "Number",
|
||||||
|
"text": "Text"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"services": {
|
|
||||||
"alarm_disarm": {
|
|
||||||
"name": "Disarm",
|
|
||||||
"description": "Disarms the alarm.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "Code",
|
|
||||||
"description": "Code to disarm the alarm."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_arm_custom_bypass": {
|
|
||||||
"name": "Arm with custom bypass",
|
|
||||||
"description": "Arms the alarm while allowing to bypass a custom area.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "Code to arm the alarm."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_arm_home": {
|
|
||||||
"name": "Arm home",
|
|
||||||
"description": "Arms the alarm in the home mode.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_arm_away": {
|
|
||||||
"name": "Arm away",
|
|
||||||
"description": "Arms the alarm in the away mode.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_arm_night": {
|
|
||||||
"name": "Arm night",
|
|
||||||
"description": "Arms the alarm in the night mode.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_arm_vacation": {
|
|
||||||
"name": "Arm vacation",
|
|
||||||
"description": "Arms the alarm in the vacation mode.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"alarm_trigger": {
|
|
||||||
"name": "Trigger",
|
|
||||||
"description": "Triggers the alarm manually.",
|
|
||||||
"fields": {
|
|
||||||
"code": {
|
|
||||||
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]",
|
|
||||||
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,5 +70,78 @@
|
|||||||
"code_arm_required": {
|
"code_arm_required": {
|
||||||
"message": "Arming requires a code but none was given for {entity_id}."
|
"message": "Arming requires a code but none was given for {entity_id}."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"services": {
|
||||||
|
"alarm_arm_away": {
|
||||||
|
"description": "Arms the alarm in the away mode.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Arm away"
|
||||||
|
},
|
||||||
|
"alarm_arm_custom_bypass": {
|
||||||
|
"description": "Arms the alarm while allowing to bypass a custom area.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "Code to arm the alarm.",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Arm with custom bypass"
|
||||||
|
},
|
||||||
|
"alarm_arm_home": {
|
||||||
|
"description": "Arms the alarm in the home mode.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Arm home"
|
||||||
|
},
|
||||||
|
"alarm_arm_night": {
|
||||||
|
"description": "Arms the alarm in the night mode.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Arm night"
|
||||||
|
},
|
||||||
|
"alarm_arm_vacation": {
|
||||||
|
"description": "Arms the alarm in the vacation mode.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Arm vacation"
|
||||||
|
},
|
||||||
|
"alarm_disarm": {
|
||||||
|
"description": "Disarms the alarm.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "Code to disarm the alarm.",
|
||||||
|
"name": "Code"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Disarm"
|
||||||
|
},
|
||||||
|
"alarm_trigger": {
|
||||||
|
"description": "Triggers the alarm manually.",
|
||||||
|
"fields": {
|
||||||
|
"code": {
|
||||||
|
"description": "[%key:component::alarm_control_panel::services::alarm_arm_custom_bypass::fields::code::description%]",
|
||||||
|
"name": "[%key:component::alarm_control_panel::services::alarm_disarm::fields::code::name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Trigger"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Alarm control panel"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +1,101 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"abort": {
|
||||||
"user": {
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
"title": "Choose AlarmDecoder protocol",
|
},
|
||||||
"data": {
|
"create_entry": {
|
||||||
"protocol": "Protocol"
|
"default": "Successfully connected to AlarmDecoder."
|
||||||
}
|
|
||||||
},
|
|
||||||
"protocol": {
|
|
||||||
"title": "Configure connection settings",
|
|
||||||
"data": {
|
|
||||||
"host": "[%key:common::config_flow::data::host%]",
|
|
||||||
"port": "[%key:common::config_flow::data::port%]",
|
|
||||||
"device_baudrate": "Device baud rate",
|
|
||||||
"device_path": "Device path"
|
|
||||||
},
|
|
||||||
"data_description": {
|
|
||||||
"host": "The hostname or IP address of the AlarmDecoder device that is connected to your alarm panel.",
|
|
||||||
"port": "The port on which AlarmDecoder is accessible (for example, 10000)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"error": {
|
"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%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"create_entry": {
|
"step": {
|
||||||
"default": "Successfully connected to AlarmDecoder."
|
"protocol": {
|
||||||
},
|
"data": {
|
||||||
"abort": {
|
"device_baudrate": "Device baud rate",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"device_path": "Device path",
|
||||||
|
"host": "[%key:common::config_flow::data::host%]",
|
||||||
|
"port": "[%key:common::config_flow::data::port%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"host": "The hostname or IP address of the AlarmDecoder device that is connected to your alarm panel.",
|
||||||
|
"port": "The port on which AlarmDecoder is accessible (for example, 10000)"
|
||||||
|
},
|
||||||
|
"title": "Configure connection settings"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"protocol": "Protocol"
|
||||||
|
},
|
||||||
|
"title": "Choose AlarmDecoder protocol"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
|
"error": {
|
||||||
|
"int": "The field below must be an integer.",
|
||||||
|
"loop_range": "'RF loop' must be an integer between 1 and 4.",
|
||||||
|
"loop_rfid": "'RF loop' cannot be used without 'RF serial'.",
|
||||||
|
"relay_inclusive": "'Relay address' and 'Relay channel' are codependent and must be included together."
|
||||||
|
},
|
||||||
"step": {
|
"step": {
|
||||||
|
"arm_settings": {
|
||||||
|
"data": {
|
||||||
|
"alt_night_mode": "Alternative night mode",
|
||||||
|
"auto_bypass": "Auto-bypass on arm",
|
||||||
|
"code_arm_required": "Code required for arming"
|
||||||
|
},
|
||||||
|
"title": "[%key:component::alarmdecoder::options::step::init::title%]"
|
||||||
|
},
|
||||||
"init": {
|
"init": {
|
||||||
"title": "Configure AlarmDecoder",
|
|
||||||
"description": "What would you like to edit?",
|
|
||||||
"data": {
|
"data": {
|
||||||
"edit_selection": "Edit"
|
"edit_selection": "Edit"
|
||||||
}
|
},
|
||||||
},
|
"description": "What would you like to edit?",
|
||||||
"arm_settings": {
|
"title": "Configure AlarmDecoder"
|
||||||
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
|
|
||||||
"data": {
|
|
||||||
"auto_bypass": "Auto-bypass on arm",
|
|
||||||
"code_arm_required": "Code required for arming",
|
|
||||||
"alt_night_mode": "Alternative night mode"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zone_select": {
|
|
||||||
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
|
|
||||||
"description": "Enter the zone number you'd like to to add, edit, or remove.",
|
|
||||||
"data": {
|
|
||||||
"zone_number": "Zone number"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"zone_details": {
|
"zone_details": {
|
||||||
"title": "[%key:component::alarmdecoder::options::step::init::title%]",
|
|
||||||
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave 'Zone name' blank.",
|
|
||||||
"data": {
|
"data": {
|
||||||
"zone_name": "Zone name",
|
|
||||||
"zone_type": "Zone type",
|
|
||||||
"zone_rfid": "RF serial",
|
|
||||||
"zone_loop": "RF loop",
|
"zone_loop": "RF loop",
|
||||||
|
"zone_name": "Zone name",
|
||||||
"zone_relayaddr": "Relay address",
|
"zone_relayaddr": "Relay address",
|
||||||
"zone_relaychan": "Relay channel"
|
"zone_relaychan": "Relay channel",
|
||||||
}
|
"zone_rfid": "RF serial",
|
||||||
|
"zone_type": "Zone type"
|
||||||
|
},
|
||||||
|
"description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave 'Zone name' blank.",
|
||||||
|
"title": "[%key:component::alarmdecoder::options::step::init::title%]"
|
||||||
|
},
|
||||||
|
"zone_select": {
|
||||||
|
"data": {
|
||||||
|
"zone_number": "Zone number"
|
||||||
|
},
|
||||||
|
"description": "Enter the zone number you'd like to to add, edit, or remove.",
|
||||||
|
"title": "[%key:component::alarmdecoder::options::step::init::title%]"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"relay_inclusive": "'Relay address' and 'Relay channel' are codependent and must be included together.",
|
|
||||||
"int": "The field below must be an integer.",
|
|
||||||
"loop_rfid": "'RF loop' cannot be used without 'RF serial'.",
|
|
||||||
"loop_range": "'RF loop' must be an integer between 1 and 4."
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"alarm_keypress": {
|
"alarm_keypress": {
|
||||||
"name": "Key press",
|
|
||||||
"description": "Sends custom keypresses to the alarm.",
|
"description": "Sends custom keypresses to the alarm.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"keypress": {
|
"keypress": {
|
||||||
"name": "[%key:component::alarmdecoder::services::alarm_keypress::name%]",
|
"description": "String to send to the alarm panel.",
|
||||||
"description": "String to send to the alarm panel."
|
"name": "[%key:component::alarmdecoder::services::alarm_keypress::name%]"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Key press"
|
||||||
},
|
},
|
||||||
"alarm_toggle_chime": {
|
"alarm_toggle_chime": {
|
||||||
"name": "Toggle chime",
|
|
||||||
"description": "Sends the alarm the toggle chime command.",
|
"description": "Sends the alarm the toggle chime command.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"code": {
|
"code": {
|
||||||
"name": "Code",
|
"description": "Code to toggle the alarm control panel chime with.",
|
||||||
"description": "Code to toggle the alarm control panel chime with."
|
"name": "Code"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"name": "Toggle chime"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "Alert",
|
|
||||||
"entity_component": {
|
"entity_component": {
|
||||||
"_": {
|
"_": {
|
||||||
"name": "[%key:component::alert::title%]",
|
"name": "[%key:component::alert::title%]",
|
||||||
@@ -12,16 +11,17 @@
|
|||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"toggle": {
|
"toggle": {
|
||||||
"name": "[%key:common::action::toggle%]",
|
"description": "Toggles alert's notifications.",
|
||||||
"description": "Toggles alert's notifications."
|
"name": "[%key:common::action::toggle%]"
|
||||||
},
|
},
|
||||||
"turn_off": {
|
"turn_off": {
|
||||||
"name": "[%key:common::action::turn_off%]",
|
"description": "Silences alert's notifications.",
|
||||||
"description": "Silences alert's notifications."
|
"name": "[%key:common::action::turn_off%]"
|
||||||
},
|
},
|
||||||
"turn_on": {
|
"turn_on": {
|
||||||
"name": "[%key:common::action::turn_on%]",
|
"description": "Resets alert's notifications.",
|
||||||
"description": "Resets alert's notifications."
|
"name": "[%key:common::action::turn_on%]"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"title": "Alert"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ from homeassistant.const import (
|
|||||||
from homeassistant.helpers import network
|
from homeassistant.helpers import network
|
||||||
from homeassistant.util import color as color_util, dt as dt_util
|
from homeassistant.util import color as color_util, dt as dt_util
|
||||||
from homeassistant.util.decorator import Registry
|
from homeassistant.util.decorator import Registry
|
||||||
from homeassistant.util.unit_conversion import TemperatureConverter
|
from homeassistant.util.unit_conversion import (
|
||||||
|
TemperatureConverter,
|
||||||
|
TemperatureDeltaConverter,
|
||||||
|
)
|
||||||
|
|
||||||
from .config import AbstractConfig
|
from .config import AbstractConfig
|
||||||
from .const import (
|
from .const import (
|
||||||
@@ -844,7 +847,7 @@ def temperature_from_object(
|
|||||||
temp -= 273.15
|
temp -= 273.15
|
||||||
|
|
||||||
if interval:
|
if interval:
|
||||||
return TemperatureConverter.convert_interval(temp, from_unit, to_unit)
|
return TemperatureDeltaConverter.convert(temp, from_unit, to_unit)
|
||||||
return TemperatureConverter.convert(temp, from_unit, to_unit)
|
return TemperatureConverter.convert(temp, from_unit, to_unit)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from collections.abc import Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonDevice
|
from aioamazondevices.const.metadata import SENSOR_STATE_OFF
|
||||||
from aioamazondevices.const import SENSOR_STATE_OFF
|
from aioamazondevices.structures import AmazonDevice
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
|||||||
data[CONF_PASSWORD],
|
data[CONF_PASSWORD],
|
||||||
)
|
)
|
||||||
|
|
||||||
return await api.login_mode_interactive(data[CONF_CODE])
|
return await api.login.login_mode_interactive(data[CONF_CODE])
|
||||||
|
|
||||||
|
|
||||||
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
|||||||
@@ -26,3 +26,6 @@ COUNTRY_DOMAINS = {
|
|||||||
"us": DEFAULT_DOMAIN,
|
"us": DEFAULT_DOMAIN,
|
||||||
"za": "co.za",
|
"za": "co.za",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CATEGORY_SENSORS = "sensors"
|
||||||
|
CATEGORY_NOTIFICATIONS = "notifications"
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonDevice, AmazonEchoApi
|
from aioamazondevices.api import AmazonEchoApi
|
||||||
from aioamazondevices.exceptions import (
|
from aioamazondevices.exceptions import (
|
||||||
CannotAuthenticate,
|
CannotAuthenticate,
|
||||||
CannotConnect,
|
CannotConnect,
|
||||||
CannotRetrieveData,
|
CannotRetrieveData,
|
||||||
)
|
)
|
||||||
|
from aioamazondevices.structures import AmazonDevice
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@@ -15,6 +16,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
|
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
|
||||||
@@ -42,6 +44,9 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
|||||||
name=entry.title,
|
name=entry.title,
|
||||||
config_entry=entry,
|
config_entry=entry,
|
||||||
update_interval=timedelta(seconds=SCAN_INTERVAL),
|
update_interval=timedelta(seconds=SCAN_INTERVAL),
|
||||||
|
request_refresh_debouncer=Debouncer(
|
||||||
|
hass, _LOGGER, cooldown=30, immediate=False
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.api = AmazonEchoApi(
|
self.api = AmazonEchoApi(
|
||||||
session,
|
session,
|
||||||
@@ -54,7 +59,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
|||||||
async def _async_update_data(self) -> dict[str, AmazonDevice]:
|
async def _async_update_data(self) -> dict[str, AmazonDevice]:
|
||||||
"""Update device data."""
|
"""Update device data."""
|
||||||
try:
|
try:
|
||||||
await self.api.login_mode_stored_data()
|
await self.api.login.login_mode_stored_data()
|
||||||
data = await self.api.get_devices_data()
|
data = await self.api.get_devices_data()
|
||||||
except CannotConnect as err:
|
except CannotConnect as err:
|
||||||
raise UpdateFailed(
|
raise UpdateFailed(
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import asdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonDevice
|
from aioamazondevices.structures import AmazonDevice
|
||||||
|
|
||||||
from homeassistant.components.diagnostics import async_redact_data
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_USERNAME
|
||||||
@@ -60,5 +61,5 @@ def build_device_data(device: AmazonDevice) -> dict[str, Any]:
|
|||||||
"online": device.online,
|
"online": device.online,
|
||||||
"serial number": device.serial_number,
|
"serial number": device.serial_number,
|
||||||
"software version": device.software_version,
|
"software version": device.software_version,
|
||||||
"sensors": device.sensors,
|
"sensors": {key: asdict(sensor) for key, sensor in device.sensors.items()},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Defines a base Alexa Devices entity."""
|
"""Defines a base Alexa Devices entity."""
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonDevice
|
from aioamazondevices.const.devices import SPEAKER_GROUP_MODEL
|
||||||
from aioamazondevices.const import SPEAKER_GROUP_MODEL
|
from aioamazondevices.structures import AmazonDevice
|
||||||
|
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioamazondevices"],
|
"loggers": ["aioamazondevices"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aioamazondevices==6.4.4"]
|
"requirements": ["aioamazondevices==9.0.2"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ from collections.abc import Awaitable, Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from aioamazondevices.api import AmazonDevice, AmazonEchoApi
|
from aioamazondevices.api import AmazonEchoApi
|
||||||
from aioamazondevices.const import SPEAKER_GROUP_FAMILY
|
from aioamazondevices.const.devices import SPEAKER_GROUP_FAMILY
|
||||||
|
from aioamazondevices.structures import AmazonDevice
|
||||||
|
|
||||||
from homeassistant.components.notify import NotifyEntity, NotifyEntityDescription
|
from homeassistant.components.notify import NotifyEntity, NotifyEntityDescription
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user