forked from home-assistant/core
Compare commits
488 Commits
llm-neverm
...
entity_reg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea7ea4b75 | ||
|
|
71f89dd759 | ||
|
|
9614a8d1ca | ||
|
|
1a60f0e668 | ||
|
|
9551a12c9c | ||
|
|
416a4c02b4 | ||
|
|
6f3a230524 | ||
|
|
25d092c8eb | ||
|
|
95107cf670 | ||
|
|
46d4081ec6 | ||
|
|
71d7e14032 | ||
|
|
f6621023c2 | ||
|
|
03c6dab143 | ||
|
|
e343b69557 | ||
|
|
ea12a7c9a7 | ||
|
|
13a37da917 | ||
|
|
b7018deebc | ||
|
|
28aa9c2fa3 | ||
|
|
30e9c45c7f | ||
|
|
611cef5cd1 | ||
|
|
d724488376 | ||
|
|
be1c225c70 | ||
|
|
28d01d88a2 | ||
|
|
4880849074 | ||
|
|
7b0a309fa7 | ||
|
|
36ce90177f | ||
|
|
f0e7cb5794 | ||
|
|
bd6df06248 | ||
|
|
e31e4c5d75 | ||
|
|
2a127d19dd | ||
|
|
790edea4a0 | ||
|
|
bcedb004be | ||
|
|
3bf4ef095d | ||
|
|
988ca114a0 | ||
|
|
b0b3f04a05 | ||
|
|
82692f9a8f | ||
|
|
a11bf5cce1 | ||
|
|
cd420aee88 | ||
|
|
3d1258ddc1 | ||
|
|
17521f25b6 | ||
|
|
1ee3b68824 | ||
|
|
53e528e9b6 | ||
|
|
397091cc7d | ||
|
|
580a8d66b2 | ||
|
|
e83a50b88d | ||
|
|
5062a7fec8 | ||
|
|
cd39e4ac80 | ||
|
|
f210b74790 | ||
|
|
b1c17334f6 | ||
|
|
020db5f822 | ||
|
|
879e082b54 | ||
|
|
d2478b4058 | ||
|
|
bd4e21aa9d | ||
|
|
1256a7ea96 | ||
|
|
1929b368fe | ||
|
|
f177336025 | ||
|
|
be34d302df | ||
|
|
f2500e5a32 | ||
|
|
772b047d44 | ||
|
|
4cb23ce562 | ||
|
|
dcbedb5ae5 | ||
|
|
abc79a9f1c | ||
|
|
07d8778870 | ||
|
|
da0454e24e | ||
|
|
2d4fe5853f | ||
|
|
3a65d1b611 | ||
|
|
af7caeae53 | ||
|
|
b139af9a9c | ||
|
|
e4ba94f939 | ||
|
|
aa7b69afd4 | ||
|
|
d3fab7d87a | ||
|
|
e91cb99512 | ||
|
|
7ba5038509 | ||
|
|
c6bcd5a036 | ||
|
|
674d42d8a0 | ||
|
|
0c08e88953 | ||
|
|
b1217f5792 | ||
|
|
9d79d905a4 | ||
|
|
85ed1d2ac8 | ||
|
|
5b06acfabd | ||
|
|
241026ef67 | ||
|
|
887f1621e5 | ||
|
|
46e513615e | ||
|
|
a20347963e | ||
|
|
21a2ce6b59 | ||
|
|
49800f9aaa | ||
|
|
3be0d0d085 | ||
|
|
786a417ac9 | ||
|
|
ac791bdd20 | ||
|
|
8d72443fd6 | ||
|
|
74eddce3d3 | ||
|
|
72de5d0fa3 | ||
|
|
bd0da03eb9 | ||
|
|
4e2e6619d0 | ||
|
|
f4e48c31bd | ||
|
|
4bb3d6123d | ||
|
|
549afbc27e | ||
|
|
b1791aae63 | ||
|
|
fa9ee2adc6 | ||
|
|
ad34082435 | ||
|
|
97cd3cd7dc | ||
|
|
6cf10cd0b2 | ||
|
|
ee8f720253 | ||
|
|
5e8012f3f5 | ||
|
|
57d5d7d2f2 | ||
|
|
31150bf897 | ||
|
|
427db02029 | ||
|
|
24b1eeb900 | ||
|
|
1ec91e7c89 | ||
|
|
9ef9f2fafb | ||
|
|
f7ce112653 | ||
|
|
e0bb044782 | ||
|
|
eddb416f6d | ||
|
|
6c3e56748c | ||
|
|
644b07d084 | ||
|
|
206cac6811 | ||
|
|
182c85cf23 | ||
|
|
9f0356fcfe | ||
|
|
ed938ba315 | ||
|
|
0b7447c562 | ||
|
|
ce8c5fc3a9 | ||
|
|
be10d79c75 | ||
|
|
d166e5fdcc | ||
|
|
421e2411d3 | ||
|
|
a4ceed776e | ||
|
|
2f0e6a6dc7 | ||
|
|
d32e69dcb6 | ||
|
|
b40d8074c0 | ||
|
|
a8713af8b8 | ||
|
|
09908153f8 | ||
|
|
e04fd48a05 | ||
|
|
b9002d0c64 | ||
|
|
acf207ad1c | ||
|
|
35fa6e5121 | ||
|
|
61fbfc3d40 | ||
|
|
16484dcee5 | ||
|
|
d2463b9e7b | ||
|
|
0d0ef6bf03 | ||
|
|
18e8b080e0 | ||
|
|
5f3bb7e89e | ||
|
|
a248a6d991 | ||
|
|
12be82fdbc | ||
|
|
5bae000db5 | ||
|
|
f02989e631 | ||
|
|
4fe8a43cc9 | ||
|
|
3fb1b8e79a | ||
|
|
552613d949 | ||
|
|
d26d483a2f | ||
|
|
40239945c1 | ||
|
|
9771998415 | ||
|
|
e54d929573 | ||
|
|
0111205f81 | ||
|
|
a661e60511 | ||
|
|
71f5f4bcdd | ||
|
|
b30795e1f4 | ||
|
|
2fd3aac268 | ||
|
|
1f8913d6cd | ||
|
|
23461d2cfd | ||
|
|
3c06fe1e21 | ||
|
|
49621aedb0 | ||
|
|
4de179c4c1 | ||
|
|
7630ea4f09 | ||
|
|
20e0913286 | ||
|
|
35438f65e5 | ||
|
|
1a0a2ebdb1 | ||
|
|
4b4c886438 | ||
|
|
0d1abc31b5 | ||
|
|
773ad6529c | ||
|
|
2eaf206562 | ||
|
|
bd9aefda62 | ||
|
|
b4d01dfd0c | ||
|
|
0c8ebbe588 | ||
|
|
4a7e6bc068 | ||
|
|
30f84f55a4 | ||
|
|
ce3db31b30 | ||
|
|
ff46b3a2bb | ||
|
|
ef55a8e665 | ||
|
|
9058e00aef | ||
|
|
60fd9d5027 | ||
|
|
28d6a21189 | ||
|
|
909b13809e | ||
|
|
88eb611eef | ||
|
|
edc857b365 | ||
|
|
0aeb8f44f4 | ||
|
|
3e98df707d | ||
|
|
841773bb68 | ||
|
|
e7f44048e9 | ||
|
|
60563ae88a | ||
|
|
ee6be6bfd6 | ||
|
|
768c2b0f3d | ||
|
|
b1379f6a89 | ||
|
|
b2ac16e95f | ||
|
|
1ca2f3393c | ||
|
|
5fdd705edf | ||
|
|
f4896f7b09 | ||
|
|
3a2460f9f9 | ||
|
|
e5851c20e9 | ||
|
|
c41cf570d3 | ||
|
|
17afe1ae51 | ||
|
|
39abeb4600 | ||
|
|
c38a33d330 | ||
|
|
7de9e9d37a | ||
|
|
52e6afdcca | ||
|
|
13a59dee5a | ||
|
|
33ad27d569 | ||
|
|
9fd23a6d30 | ||
|
|
5137b06ee7 | ||
|
|
f68b78d00e | ||
|
|
1456d5802d | ||
|
|
84e6c0b9ac | ||
|
|
94b16da90f | ||
|
|
950563cf32 | ||
|
|
437111453b | ||
|
|
106c5d4248 | ||
|
|
de0ffea52d | ||
|
|
80ad154dcd | ||
|
|
2b40844171 | ||
|
|
e55d8b2d2b | ||
|
|
2977cf227e | ||
|
|
719cbd3070 | ||
|
|
9b90df74a6 | ||
|
|
bd40e1e7df | ||
|
|
dcdf033fa9 | ||
|
|
8910dbbcd1 | ||
|
|
bd1ad04dab | ||
|
|
d92dbbf58b | ||
|
|
b3ff8f56b9 | ||
|
|
b6b340ae63 | ||
|
|
5c60cffd4d | ||
|
|
4c3ae395a4 | ||
|
|
333ada7670 | ||
|
|
4fd4ba7813 | ||
|
|
7e96666dc5 | ||
|
|
e463d5d16f | ||
|
|
f28579357e | ||
|
|
d40a9bd9ef | ||
|
|
28ecee6479 | ||
|
|
512ac7d572 | ||
|
|
22b353f7d5 | ||
|
|
49c40cd902 | ||
|
|
629c7a53ce | ||
|
|
66e3ffffa7 | ||
|
|
139b424717 | ||
|
|
8f43a71ff6 | ||
|
|
02db5ec88f | ||
|
|
d88f6dc6b9 | ||
|
|
977d8fd1c8 | ||
|
|
deab285db8 | ||
|
|
a417d3dcf8 | ||
|
|
545a780fcb | ||
|
|
5a1d5802c4 | ||
|
|
f0c07d68c5 | ||
|
|
db266d80ec | ||
|
|
8c6d638354 | ||
|
|
ea9301aa9e | ||
|
|
33633f885d | ||
|
|
2ebc229d8d | ||
|
|
5600ad0d82 | ||
|
|
cafd2092d4 | ||
|
|
6b7724c556 | ||
|
|
ab1f03f392 | ||
|
|
58d06ebc39 | ||
|
|
ce11ac5ecd | ||
|
|
cb36184511 | ||
|
|
1fe2a928a2 | ||
|
|
7a98497710 | ||
|
|
3ef9b71807 | ||
|
|
3b39c53479 | ||
|
|
c0303bc652 | ||
|
|
2696405c63 | ||
|
|
9a17389cd0 | ||
|
|
c484568e75 | ||
|
|
5b365fc0bd | ||
|
|
abd3466d19 | ||
|
|
535b47789f | ||
|
|
4deaeaeda0 | ||
|
|
5ae875be77 | ||
|
|
14897f921c | ||
|
|
f31ff3ca14 | ||
|
|
bb51837346 | ||
|
|
a405d2b724 | ||
|
|
1a714276cc | ||
|
|
09d7fed6cd | ||
|
|
b9e4855e05 | ||
|
|
ab83ec61e0 | ||
|
|
74b713fa97 | ||
|
|
e401fee3da | ||
|
|
759a2b84f5 | ||
|
|
6c98cd49ea | ||
|
|
ebffcb455f | ||
|
|
08773cefb7 | ||
|
|
79352ea0f0 | ||
|
|
b7038d4eb7 | ||
|
|
8a310cbbf8 | ||
|
|
07196b0fda | ||
|
|
0a38af7e48 | ||
|
|
155fafb735 | ||
|
|
54ec41f25d | ||
|
|
f480cc3396 | ||
|
|
2aea738032 | ||
|
|
ab5165fdfa | ||
|
|
c6468aca2b | ||
|
|
895ffbabf7 | ||
|
|
3f1286b338 | ||
|
|
d3a577ad89 | ||
|
|
f44103ac7f | ||
|
|
f1ebda7c6f | ||
|
|
905769f0e8 | ||
|
|
43899b6f28 | ||
|
|
b5e7da4262 | ||
|
|
3dc0ca7e1e | ||
|
|
42c46a15b4 | ||
|
|
97a725c2c6 | ||
|
|
c3499e5294 | ||
|
|
110935461e | ||
|
|
208b14dd2b | ||
|
|
13e4c51ce5 | ||
|
|
56fc8a1f92 | ||
|
|
92f38ef1a1 | ||
|
|
33db95f6be | ||
|
|
7ae80b542a | ||
|
|
6fc4f45def | ||
|
|
7c9b8552cb | ||
|
|
6a09474623 | ||
|
|
e3885b8117 | ||
|
|
9e723752f8 | ||
|
|
7e07930342 | ||
|
|
76ba3afeae | ||
|
|
af5574f71c | ||
|
|
ff77ecd2ce | ||
|
|
f6beefced3 | ||
|
|
f59cf8fa54 | ||
|
|
3e64d148cc | ||
|
|
c4ba15bb8c | ||
|
|
bebbb87aa2 | ||
|
|
3a19c2f47f | ||
|
|
ffccdbbcec | ||
|
|
d66a6d9596 | ||
|
|
50936b4e28 | ||
|
|
aeab8a0143 | ||
|
|
003d4d712a | ||
|
|
3e2bac96e6 | ||
|
|
39b2cf6ed2 | ||
|
|
bb7dc079ce | ||
|
|
101bb091ba | ||
|
|
03be1b9f38 | ||
|
|
5dadabe50c | ||
|
|
0e5b03b343 | ||
|
|
db430beb5b | ||
|
|
0a977d070b | ||
|
|
e1772d25f2 | ||
|
|
755d36d82f | ||
|
|
32b8c8985e | ||
|
|
e52182940b | ||
|
|
d7cdb357dc | ||
|
|
54c5d1002b | ||
|
|
13e9f1935d | ||
|
|
92520fe365 | ||
|
|
2f644eb61c | ||
|
|
4b9d89a480 | ||
|
|
fe0f414e99 | ||
|
|
89ee49e50c | ||
|
|
6db8fced60 | ||
|
|
99063ba141 | ||
|
|
0c693b6ae1 | ||
|
|
c610f16e90 | ||
|
|
29b48d02de | ||
|
|
a419fde0eb | ||
|
|
be40db3dff | ||
|
|
c3c500955a | ||
|
|
1e5a5925e6 | ||
|
|
d956e4b11d | ||
|
|
8ff8cd8b65 | ||
|
|
fab35f227d | ||
|
|
e4d19541f5 | ||
|
|
6b6fc6bbeb | ||
|
|
f2bafee84a | ||
|
|
4e0cdb0537 | ||
|
|
79c919f62d | ||
|
|
b6dec11487 | ||
|
|
e2073d7762 | ||
|
|
1cf00d9bbc | ||
|
|
ea7f1b2a4e | ||
|
|
11a2a62144 | ||
|
|
3d26fa7864 | ||
|
|
e37ae8bf8d | ||
|
|
56ec70815c | ||
|
|
584bb7bca8 | ||
|
|
79ed6d865f | ||
|
|
66d0d2eb6c | ||
|
|
8d1493036a | ||
|
|
5dc390b6b9 | ||
|
|
4eb75a56e6 | ||
|
|
4eb5734d73 | ||
|
|
5458ee2fa9 | ||
|
|
28eb4f3dff | ||
|
|
80f28302a1 | ||
|
|
782fff198c | ||
|
|
b6458ff9b8 | ||
|
|
c6cd7e38f7 | ||
|
|
c2e6f8e761 | ||
|
|
b17b1f6db8 | ||
|
|
b94a47ceb2 | ||
|
|
86f8b5893f | ||
|
|
78ced997e2 | ||
|
|
bd8cd87fae | ||
|
|
521505f9b5 | ||
|
|
98734ebe4f | ||
|
|
ffc3aca41f | ||
|
|
8fdd095dab | ||
|
|
36ca4e8866 | ||
|
|
e706a5ef27 | ||
|
|
82e190dc4b | ||
|
|
bd3f432376 | ||
|
|
ff1702eefa | ||
|
|
cf0ee63507 | ||
|
|
c54eed3607 | ||
|
|
a0541c7fe6 | ||
|
|
2b094ee25d | ||
|
|
3aae9b629f | ||
|
|
c55a4e9584 | ||
|
|
8343d7f348 | ||
|
|
fd42c01a21 | ||
|
|
bc7cfb6761 | ||
|
|
598ce1f3b0 | ||
|
|
8878d0f0e1 | ||
|
|
47aebabc51 | ||
|
|
37972ec88e | ||
|
|
6103cea3f5 | ||
|
|
d7428786cd | ||
|
|
673bdcc556 | ||
|
|
e8ef990e72 | ||
|
|
0d155c416a | ||
|
|
e48be5c406 | ||
|
|
787a1613ec | ||
|
|
bb847b346d | ||
|
|
e9b34eaad0 | ||
|
|
572347025b | ||
|
|
29e80e56c6 | ||
|
|
b60b2fdd7c | ||
|
|
aaf3f61675 | ||
|
|
5bf972ff16 | ||
|
|
8eb52edabf | ||
|
|
4326689f52 | ||
|
|
ffeefd4856 | ||
|
|
44ed83a829 | ||
|
|
a0d5fda4b6 | ||
|
|
2b907ee56e | ||
|
|
bcdac7ed37 | ||
|
|
6da2515d7a | ||
|
|
6c6980a550 | ||
|
|
bd29aaffb8 | ||
|
|
74522390ad | ||
|
|
92204e6c92 | ||
|
|
5d71533c7b | ||
|
|
9209e43e4c | ||
|
|
2c1a754e5d | ||
|
|
06838c0280 | ||
|
|
f97d96e3ae | ||
|
|
ee960933db | ||
|
|
2ea0c54788 | ||
|
|
dd18672341 | ||
|
|
ac4ae0430e | ||
|
|
eeb63d42a0 | ||
|
|
9d48f36754 | ||
|
|
157198bf41 | ||
|
|
be25b9d4d0 | ||
|
|
e08b71086f | ||
|
|
9677c6e24c | ||
|
|
e2cda54473 | ||
|
|
3ca49dc8a6 | ||
|
|
80bc70771e | ||
|
|
7ab1bfcf1f | ||
|
|
99f8dbd278 | ||
|
|
3af0bc2c33 | ||
|
|
b8c4ce932c | ||
|
|
0a3a3edf77 | ||
|
|
71376229f6 | ||
|
|
c9dde419a2 | ||
|
|
2fc01a02db | ||
|
|
f02d2344fc | ||
|
|
509311ac19 | ||
|
|
47e7c4f1c1 | ||
|
|
c9d3ba900e | ||
|
|
74a3d11aea | ||
|
|
897abc114e | ||
|
|
3fff3003f2 | ||
|
|
db5c93f96d |
6
.github/workflows/builder.yml
vendored
6
.github/workflows/builder.yml
vendored
@@ -94,7 +94,7 @@ jobs:
|
||||
|
||||
- name: Download nightly wheels of frontend
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
repo: home-assistant/frontend
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
|
||||
- name: Download nightly wheels of intents
|
||||
if: needs.init.outputs.channel == 'dev'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
repo: home-assistant/intents-package
|
||||
@@ -531,7 +531,7 @@ jobs:
|
||||
|
||||
- name: Generate artifact attestation
|
||||
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
|
||||
uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4
|
||||
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0
|
||||
with:
|
||||
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
|
||||
69
.github/workflows/ci.yaml
vendored
69
.github/workflows/ci.yaml
vendored
@@ -240,7 +240,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.1.2
|
||||
uses: actions/cache@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
key: >-
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
uv pip install "$(cat requirements_test.txt | grep pre-commit)"
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.1.2
|
||||
uses: actions/cache@v4.2.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
lookup-only: true
|
||||
@@ -286,7 +286,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -326,7 +326,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -335,7 +335,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -375,7 +375,7 @@ jobs:
|
||||
needs.info.outputs.pre-commit_cache_key }}
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
fail-on-cache-miss: true
|
||||
@@ -482,16 +482,15 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.1.2
|
||||
uses: actions/cache@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
lookup-only: true
|
||||
key: >-
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore uv wheel cache
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
uses: actions/cache@v4.1.2
|
||||
uses: actions/cache@v4.2.0
|
||||
with:
|
||||
path: ${{ env.UV_CACHE_DIR }}
|
||||
key: >-
|
||||
@@ -531,6 +530,26 @@ jobs:
|
||||
python -m script.gen_requirements_all ci
|
||||
uv pip install -r requirements_all_pytest.txt -r requirements_test.txt
|
||||
uv pip install -e . --config-settings editable_mode=compat
|
||||
- name: Dump pip freeze
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
uv pip freeze >> pip_freeze.txt
|
||||
- name: Upload pip_freeze artifact
|
||||
uses: actions/upload-artifact@v4.4.3
|
||||
with:
|
||||
name: pip-freeze-${{ matrix.python-version }}
|
||||
path: pip_freeze.txt
|
||||
overwrite: true
|
||||
- name: Remove pip_freeze
|
||||
run: rm pip_freeze.txt
|
||||
- name: Remove generated requirements_all
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: rm requirements_all_pytest.txt requirements_all_wheels_*.txt
|
||||
- name: Check dirty
|
||||
run: |
|
||||
./script/check_dirty
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
@@ -559,7 +578,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -592,7 +611,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -630,7 +649,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -673,7 +692,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -720,7 +739,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -772,7 +791,7 @@ jobs:
|
||||
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
|
||||
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -780,7 +799,7 @@ jobs:
|
||||
${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
|
||||
needs.info.outputs.python_cache_key }}
|
||||
- name: Restore mypy cache
|
||||
uses: actions/cache@v4.1.2
|
||||
uses: actions/cache@v4.2.0
|
||||
with:
|
||||
path: .mypy_cache
|
||||
key: >-
|
||||
@@ -846,7 +865,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore base Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -910,7 +929,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1031,7 +1050,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1160,7 +1179,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1254,7 +1273,7 @@ jobs:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'true'
|
||||
uses: codecov/codecov-action@v5.0.7
|
||||
uses: codecov/codecov-action@v5.1.1
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
flags: full-suite
|
||||
@@ -1306,7 +1325,7 @@ jobs:
|
||||
check-latest: true
|
||||
- name: Restore full Python ${{ matrix.python-version }} virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache/restore@v4.1.2
|
||||
uses: actions/cache/restore@v4.2.0
|
||||
with:
|
||||
path: venv
|
||||
fail-on-cache-miss: true
|
||||
@@ -1392,7 +1411,7 @@ jobs:
|
||||
pattern: coverage-*
|
||||
- name: Upload coverage to Codecov
|
||||
if: needs.info.outputs.test_full_suite == 'false'
|
||||
uses: codecov/codecov-action@v5.0.7
|
||||
uses: codecov/codecov-action@v5.1.1
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.27.5
|
||||
uses: github/codeql-action/init@v3.27.6
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.27.5
|
||||
uses: github/codeql-action/analyze@v3.27.6
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
||||
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-dev"
|
||||
skip-binary: aiohttp;multidict;yarl
|
||||
skip-binary: aiohttp;multidict;propcache;yarl;SQLAlchemy
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements.txt"
|
||||
|
||||
@@ -365,6 +365,7 @@ homeassistant.components.persistent_notification.*
|
||||
homeassistant.components.pi_hole.*
|
||||
homeassistant.components.ping.*
|
||||
homeassistant.components.plugwise.*
|
||||
homeassistant.components.powerfox.*
|
||||
homeassistant.components.powerwall.*
|
||||
homeassistant.components.private_ble_device.*
|
||||
homeassistant.components.prometheus.*
|
||||
@@ -439,7 +440,6 @@ homeassistant.components.ssdp.*
|
||||
homeassistant.components.starlink.*
|
||||
homeassistant.components.statistics.*
|
||||
homeassistant.components.steamist.*
|
||||
homeassistant.components.stookalert.*
|
||||
homeassistant.components.stookwijzer.*
|
||||
homeassistant.components.stream.*
|
||||
homeassistant.components.streamlabswater.*
|
||||
|
||||
28
.vscode/tasks.json
vendored
28
.vscode/tasks.json
vendored
@@ -16,7 +16,7 @@
|
||||
{
|
||||
"label": "Pytest",
|
||||
"type": "shell",
|
||||
"command": "python3 -m pytest --timeout=10 tests",
|
||||
"command": "${command:python.interpreterPath} -m pytest --timeout=10 tests",
|
||||
"dependsOn": ["Install all Test Requirements"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
@@ -31,7 +31,7 @@
|
||||
{
|
||||
"label": "Pytest (changed tests only)",
|
||||
"type": "shell",
|
||||
"command": "python3 -m pytest --timeout=10 --picked",
|
||||
"command": "${command:python.interpreterPath} -m pytest --timeout=10 --picked",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
@@ -56,6 +56,20 @@
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pre-commit",
|
||||
"type": "shell",
|
||||
"command": "pre-commit run --show-diff-on-failure",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "new"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Pylint",
|
||||
"type": "shell",
|
||||
@@ -75,7 +89,7 @@
|
||||
"label": "Code Coverage",
|
||||
"detail": "Generate code coverage report for a given integration.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
|
||||
"command": "${command:python.interpreterPath} -m pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing --durations-min=1 --durations=0 --numprocesses=auto",
|
||||
"dependsOn": ["Compile English translations"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
@@ -91,7 +105,7 @@
|
||||
"label": "Update syrupy snapshots",
|
||||
"detail": "Update syrupy snapshots for a given integration.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m pytest ./tests/components/${input:integrationName} --snapshot-update",
|
||||
"command": "${command:python.interpreterPath} -m pytest ./tests/components/${input:integrationName} --snapshot-update",
|
||||
"dependsOn": ["Compile English translations"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
@@ -149,7 +163,7 @@
|
||||
"label": "Compile English translations",
|
||||
"detail": "In order to test changes to translation files, the translation strings must be compiled into Home Assistant's translation directories.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m script.translations develop --all",
|
||||
"command": "${command:python.interpreterPath} -m script.translations develop --all",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
@@ -159,7 +173,7 @@
|
||||
"label": "Run scaffold",
|
||||
"detail": "Add new functionality to a integration using a scaffold.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m script.scaffold ${input:scaffoldName} --integration ${input:integrationName}",
|
||||
"command": "${command:python.interpreterPath} -m script.scaffold ${input:scaffoldName} --integration ${input:integrationName}",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
@@ -169,7 +183,7 @@
|
||||
"label": "Create new integration",
|
||||
"detail": "Use the scaffold to create a new integration.",
|
||||
"type": "shell",
|
||||
"command": "python3 -m script.scaffold integration",
|
||||
"command": "${command:python.interpreterPath} -m script.scaffold integration",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
|
||||
14
CODEOWNERS
14
CODEOWNERS
@@ -753,6 +753,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ista_ecotrend/ @tr4nt0r
|
||||
/homeassistant/components/isy994/ @bdraco @shbatm
|
||||
/tests/components/isy994/ @bdraco @shbatm
|
||||
/homeassistant/components/ituran/ @shmuelzon
|
||||
/tests/components/ituran/ @shmuelzon
|
||||
/homeassistant/components/izone/ @Swamp-Ig
|
||||
/tests/components/izone/ @Swamp-Ig
|
||||
/homeassistant/components/jellyfin/ @j-stienstra @ctalkington
|
||||
@@ -1004,6 +1006,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/nice_go/ @IceBotYT
|
||||
/homeassistant/components/nightscout/ @marciogranzotto
|
||||
/tests/components/nightscout/ @marciogranzotto
|
||||
/homeassistant/components/niko_home_control/ @VandeurenGlenn
|
||||
/tests/components/niko_home_control/ @VandeurenGlenn
|
||||
/homeassistant/components/nilu/ @hfurubotten
|
||||
/homeassistant/components/nina/ @DeerMaximum
|
||||
/tests/components/nina/ @DeerMaximum
|
||||
@@ -1131,6 +1135,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/point/ @fredrike
|
||||
/homeassistant/components/poolsense/ @haemishkyd
|
||||
/tests/components/poolsense/ @haemishkyd
|
||||
/homeassistant/components/powerfox/ @klaasnicolaas
|
||||
/tests/components/powerfox/ @klaasnicolaas
|
||||
/homeassistant/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/tests/components/powerwall/ @bdraco @jrester @daniel-simpson
|
||||
/homeassistant/components/private_ble_device/ @Jc2k
|
||||
@@ -1411,15 +1417,13 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/starline/ @anonym-tsk
|
||||
/homeassistant/components/starlink/ @boswelja
|
||||
/tests/components/starlink/ @boswelja
|
||||
/homeassistant/components/statistics/ @ThomDietrich
|
||||
/tests/components/statistics/ @ThomDietrich
|
||||
/homeassistant/components/statistics/ @ThomDietrich @gjohansson-ST
|
||||
/tests/components/statistics/ @ThomDietrich @gjohansson-ST
|
||||
/homeassistant/components/steam_online/ @tkdrob
|
||||
/tests/components/steam_online/ @tkdrob
|
||||
/homeassistant/components/steamist/ @bdraco
|
||||
/tests/components/steamist/ @bdraco
|
||||
/homeassistant/components/stiebel_eltron/ @fucm
|
||||
/homeassistant/components/stookalert/ @fwestenberg @frenck
|
||||
/tests/components/stookalert/ @fwestenberg @frenck
|
||||
/homeassistant/components/stookwijzer/ @fwestenberg
|
||||
/tests/components/stookwijzer/ @fwestenberg
|
||||
/homeassistant/components/stream/ @hunterjm @uvjustin @allenporter
|
||||
@@ -1642,6 +1646,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/waqi/ @joostlek
|
||||
/homeassistant/components/water_heater/ @home-assistant/core
|
||||
/tests/components/water_heater/ @home-assistant/core
|
||||
/homeassistant/components/watergate/ @adam-the-hero
|
||||
/tests/components/watergate/ @adam-the-hero
|
||||
/homeassistant/components/watson_tts/ @rutkai
|
||||
/homeassistant/components/watttime/ @bachya
|
||||
/tests/components/watttime/ @bachya
|
||||
|
||||
@@ -9,7 +9,7 @@ from jaraco.abode.devices.light import Light
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_HS_COLOR,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
@@ -17,10 +17,6 @@ from homeassistant.components.light import (
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.color import (
|
||||
color_temperature_kelvin_to_mired,
|
||||
color_temperature_mired_to_kelvin,
|
||||
)
|
||||
|
||||
from . import AbodeSystem
|
||||
from .const import DOMAIN
|
||||
@@ -47,10 +43,8 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
if ATTR_COLOR_TEMP in kwargs and self._device.is_color_capable:
|
||||
self._device.set_color_temp(
|
||||
int(color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]))
|
||||
)
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs and self._device.is_color_capable:
|
||||
self._device.set_color_temp(kwargs[ATTR_COLOR_TEMP_KELVIN])
|
||||
return
|
||||
|
||||
if ATTR_HS_COLOR in kwargs and self._device.is_color_capable:
|
||||
@@ -85,10 +79,10 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
return None
|
||||
|
||||
@property
|
||||
def color_temp(self) -> int | None:
|
||||
def color_temp_kelvin(self) -> int | None:
|
||||
"""Return the color temp of the light."""
|
||||
if self._device.has_color:
|
||||
return color_temperature_kelvin_to_mired(self._device.color_temp)
|
||||
return int(self._device.color_temp)
|
||||
return None
|
||||
|
||||
@property
|
||||
|
||||
@@ -16,6 +16,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from .coordinator import AcaiaConfigEntry
|
||||
from .entity import AcaiaEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AcaiaBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
|
||||
@@ -42,7 +42,7 @@ class AcaiaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
mac = format_mac(user_input[CONF_ADDRESS])
|
||||
mac = user_input[CONF_ADDRESS]
|
||||
try:
|
||||
is_new_style_scale = await is_new_scale(mac)
|
||||
except AcaiaDeviceNotFound:
|
||||
@@ -53,12 +53,12 @@ class AcaiaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
except AcaiaUnknownDevice:
|
||||
return self.async_abort(reason="unsupported_device")
|
||||
else:
|
||||
await self.async_set_unique_id(mac)
|
||||
await self.async_set_unique_id(format_mac(mac))
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
if not errors:
|
||||
return self.async_create_entry(
|
||||
title=self._discovered_devices[user_input[CONF_ADDRESS]],
|
||||
title=self._discovered_devices[mac],
|
||||
data={
|
||||
CONF_ADDRESS: mac,
|
||||
CONF_IS_NEW_STYLE_SCALE: is_new_style_scale,
|
||||
@@ -99,10 +99,10 @@ class AcaiaConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a discovered Bluetooth device."""
|
||||
|
||||
self._discovered[CONF_ADDRESS] = mac = format_mac(discovery_info.address)
|
||||
self._discovered[CONF_ADDRESS] = discovery_info.address
|
||||
self._discovered[CONF_NAME] = discovery_info.name
|
||||
|
||||
await self.async_set_unique_id(mac)
|
||||
await self.async_set_unique_id(format_mac(discovery_info.address))
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_BLUETOOTH,
|
||||
DeviceInfo,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@@ -25,13 +29,15 @@ class AcaiaEntity(CoordinatorEntity[AcaiaCoordinator]):
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = entity_description
|
||||
self._scale = coordinator.scale
|
||||
self._attr_unique_id = f"{self._scale.mac}_{entity_description.key}"
|
||||
formatted_mac = format_mac(self._scale.mac)
|
||||
self._attr_unique_id = f"{formatted_mac}_{entity_description.key}"
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._scale.mac)},
|
||||
identifiers={(DOMAIN, formatted_mac)},
|
||||
manufacturer="Acaia",
|
||||
model=self._scale.model,
|
||||
suggested_area="Kitchen",
|
||||
connections={(CONNECTION_BLUETOOTH, self._scale.mac)},
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioacaia"],
|
||||
"requirements": ["aioacaia==0.1.10"]
|
||||
"requirements": ["aioacaia==0.1.11"]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from .coordinator import AcaiaConfigEntry
|
||||
from .entity import AcaiaEntity
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class AcaiaSensorEntityDescription(SensorEntityDescription):
|
||||
|
||||
@@ -75,7 +75,6 @@ class AdaxDevice(ClimateEntity):
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
|
||||
"""Initialize the heater."""
|
||||
|
||||
@@ -102,7 +102,6 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
_support_preset = ClimateEntityFeature(0)
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str) -> None:
|
||||
@@ -261,7 +260,6 @@ class AdvantageAirZone(AdvantageAirZoneEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, ac_key: str, zone_key: str) -> None:
|
||||
"""Initialize an AdvantageAir Zone control."""
|
||||
|
||||
@@ -95,7 +95,6 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, ac_number, info):
|
||||
"""Initialize the climate device."""
|
||||
@@ -205,7 +204,6 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = AT_GROUP_MODES
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, group_number, info):
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -124,7 +124,6 @@ class Airtouch5ClimateEntity(ClimateEntity, Airtouch5Entity):
|
||||
_attr_translation_key = DOMAIN
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
|
||||
class Airtouch5AC(Airtouch5ClimateEntity):
|
||||
|
||||
@@ -136,7 +136,6 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
_attr_name = None
|
||||
_speeds: dict[int, str] = {}
|
||||
_speeds_reverse: dict[str, int] = {}
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -177,7 +177,6 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
|
||||
_attr_name = None
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def _init_attributes(self) -> None:
|
||||
"""Init common climate device attributes."""
|
||||
@@ -194,12 +193,6 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
|
||||
)
|
||||
|
||||
if (
|
||||
self.get_airzone_value(AZD_SPEED) is not None
|
||||
and self.get_airzone_value(AZD_SPEEDS) is not None
|
||||
):
|
||||
self._initialize_fan_speeds()
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Update attributes when the coordinator updates."""
|
||||
@@ -214,8 +207,6 @@ class AirzoneClimate(AirzoneEntity, ClimateEntity):
|
||||
self._attr_hvac_action = HVAC_ACTION_LIB_TO_HASS[
|
||||
self.get_airzone_value(AZD_ACTION)
|
||||
]
|
||||
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
||||
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
|
||||
if self.get_airzone_value(AZD_POWER):
|
||||
self._attr_hvac_mode = HVAC_MODE_LIB_TO_HASS[
|
||||
self.get_airzone_value(AZD_MODE)
|
||||
@@ -252,6 +243,22 @@ class AirzoneDeviceClimate(AirzoneClimate):
|
||||
_speeds: dict[int, str]
|
||||
_speeds_reverse: dict[str, int]
|
||||
|
||||
def _init_attributes(self) -> None:
|
||||
"""Init common climate device attributes."""
|
||||
super()._init_attributes()
|
||||
if (
|
||||
self.get_airzone_value(AZD_SPEED) is not None
|
||||
and self.get_airzone_value(AZD_SPEEDS) is not None
|
||||
):
|
||||
self._initialize_fan_speeds()
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update climate attributes."""
|
||||
super()._async_update_attrs()
|
||||
if self.supported_features & ClimateEntityFeature.FAN_MODE:
|
||||
self._attr_fan_mode = self._speeds.get(self.get_airzone_value(AZD_SPEED))
|
||||
|
||||
def _initialize_fan_speeds(self) -> None:
|
||||
"""Initialize fan speeds."""
|
||||
azd_speeds: dict[int, int] = self.get_airzone_value(AZD_SPEEDS)
|
||||
|
||||
@@ -355,12 +355,7 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
@cached_property
|
||||
def supported_features(self) -> AlarmControlPanelEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
features = self._attr_supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = AlarmControlPanelEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
return self._attr_supported_features
|
||||
|
||||
@final
|
||||
@property
|
||||
|
||||
@@ -436,7 +436,7 @@ class AlexaPowerController(AlexaCapability):
|
||||
elif self.entity.domain == remote.DOMAIN:
|
||||
is_on = self.entity.state not in (STATE_OFF, STATE_UNKNOWN)
|
||||
elif self.entity.domain == vacuum.DOMAIN:
|
||||
is_on = self.entity.state == vacuum.STATE_CLEANING
|
||||
is_on = self.entity.state == vacuum.VacuumActivity.CLEANING
|
||||
elif self.entity.domain == timer.DOMAIN:
|
||||
is_on = self.entity.state != STATE_IDLE
|
||||
elif self.entity.domain == water_heater.DOMAIN:
|
||||
|
||||
@@ -359,7 +359,7 @@ async def async_api_set_color_temperature(
|
||||
await hass.services.async_call(
|
||||
entity.domain,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: entity.entity_id, light.ATTR_KELVIN: kelvin},
|
||||
{ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP_KELVIN: kelvin},
|
||||
blocking=False,
|
||||
context=context,
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ def _setup_androidtv(
|
||||
adb_log = f"using Python ADB implementation with adbkey='{adbkey}'"
|
||||
|
||||
else:
|
||||
# Use "pure-python-adb" (communicate with ADB server)
|
||||
# Communicate via ADB server
|
||||
signer = None
|
||||
adb_log = (
|
||||
"using ADB server at"
|
||||
|
||||
@@ -151,5 +151,5 @@ class AndroidTVEntity(Entity):
|
||||
# Using "adb_shell" (Python ADB implementation)
|
||||
self.exceptions = ADB_PYTHON_EXCEPTIONS
|
||||
else:
|
||||
# Using "pure-python-adb" (communicate with ADB server)
|
||||
# Communicate via ADB server
|
||||
self.exceptions = ADB_TCP_EXCEPTIONS
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["adb_shell", "androidtv", "pure_python_adb"],
|
||||
"requirements": [
|
||||
"adb-shell[async]==0.4.4",
|
||||
"androidtv[async]==0.0.75",
|
||||
"pure-python-adb[async]==0.3.0.dev0"
|
||||
]
|
||||
"loggers": ["adb_shell", "androidtv"],
|
||||
"requirements": ["adb-shell[async]==0.4.4", "androidtv[async]==0.0.75"]
|
||||
}
|
||||
|
||||
@@ -5,12 +5,17 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from APsystemsEZ1 import APsystemsEZ1M, ReturnAlarmInfo, ReturnOutputData
|
||||
from APsystemsEZ1 import (
|
||||
APsystemsEZ1M,
|
||||
InverterReturnedError,
|
||||
ReturnAlarmInfo,
|
||||
ReturnOutputData,
|
||||
)
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -43,6 +48,11 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
|
||||
self.api.min_power = device_info.minPower
|
||||
|
||||
async def _async_update_data(self) -> ApSystemsSensorData:
|
||||
output_data = await self.api.get_output_data()
|
||||
alarm_info = await self.api.get_alarm_info()
|
||||
try:
|
||||
output_data = await self.api.get_output_data()
|
||||
alarm_info = await self.api.get_alarm_info()
|
||||
except InverterReturnedError:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN, translation_key="inverter_error"
|
||||
) from None
|
||||
return ApSystemsSensorData(output_data=output_data, alarm_info=alarm_info)
|
||||
|
||||
@@ -72,5 +72,10 @@
|
||||
"name": "Inverter status"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"inverter_error": {
|
||||
"message": "Inverter returned an error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1018,7 @@ class PipelineRun:
|
||||
"intent_input": intent_input,
|
||||
"conversation_id": conversation_id,
|
||||
"device_id": device_id,
|
||||
"prefer_local_intents": self.pipeline.prefer_local_intents,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -1031,6 +1032,7 @@ class PipelineRun:
|
||||
language=self.pipeline.language,
|
||||
agent_id=self.intent_agent,
|
||||
)
|
||||
processed_locally = self.intent_agent == conversation.HOME_ASSISTANT_AGENT
|
||||
|
||||
conversation_result: conversation.ConversationResult | None = None
|
||||
if user_input.agent_id != conversation.HOME_ASSISTANT_AGENT:
|
||||
@@ -1061,6 +1063,7 @@ class PipelineRun:
|
||||
response=intent_response,
|
||||
conversation_id=user_input.conversation_id,
|
||||
)
|
||||
processed_locally = True
|
||||
|
||||
if conversation_result is None:
|
||||
# Fall back to pipeline conversation agent
|
||||
@@ -1085,7 +1088,10 @@ class PipelineRun:
|
||||
self.process_event(
|
||||
PipelineEvent(
|
||||
PipelineEventType.INTENT_END,
|
||||
{"intent_output": conversation_result.as_dict()},
|
||||
{
|
||||
"processed_locally": processed_locally,
|
||||
"intent_output": conversation_result.as_dict(),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class AtagThermostat(AtagEntity, ClimateEntity):
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None:
|
||||
"""Initialize an Atag climate device."""
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==2.5.1"]
|
||||
"requirements": ["yalexs==8.10.0", "yalexs-ble==2.5.2"]
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from autarco import Autarco
|
||||
from autarco import Autarco, AutarcoConnectionError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import AutarcoDataUpdateCoordinator
|
||||
@@ -25,7 +26,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: AutarcoConfigEntry) -> b
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
session=async_get_clientsession(hass),
|
||||
)
|
||||
account_sites = await client.get_account()
|
||||
|
||||
try:
|
||||
account_sites = await client.get_account()
|
||||
except AutarcoConnectionError as err:
|
||||
await client.close()
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
coordinators: list[AutarcoDataUpdateCoordinator] = [
|
||||
AutarcoDataUpdateCoordinator(hass, client, site) for site in account_sites
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from autarco import Autarco, AutarcoAuthenticationError, AutarcoConnectionError
|
||||
@@ -20,6 +21,12 @@ DATA_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
STEP_REAUTH_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AutarcoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Autarco."""
|
||||
@@ -55,3 +62,40 @@ class AutarcoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
data_schema=DATA_SCHEMA,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle re-authentication request from Autarco."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle re-authentication confirmation."""
|
||||
errors = {}
|
||||
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
if user_input is not None:
|
||||
client = Autarco(
|
||||
email=reauth_entry.data[CONF_EMAIL],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
session=async_get_clientsession(self.hass),
|
||||
)
|
||||
try:
|
||||
await client.get_account()
|
||||
except AutarcoAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except AutarcoConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
else:
|
||||
return self.async_update_reload_and_abort(
|
||||
reauth_entry,
|
||||
data_updates=user_input,
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
description_placeholders={"email": reauth_entry.data[CONF_EMAIL]},
|
||||
data_schema=STEP_REAUTH_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import NamedTuple
|
||||
from autarco import (
|
||||
AccountSite,
|
||||
Autarco,
|
||||
AutarcoAuthenticationError,
|
||||
AutarcoConnectionError,
|
||||
Battery,
|
||||
Inverter,
|
||||
@@ -16,6 +17,7 @@ from autarco import (
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
||||
@@ -60,8 +62,10 @@ class AutarcoDataUpdateCoordinator(DataUpdateCoordinator[AutarcoData]):
|
||||
inverters = await self.client.get_inverters(self.account_site.public_key)
|
||||
if site.has_battery:
|
||||
battery = await self.client.get_battery(self.account_site.public_key)
|
||||
except AutarcoConnectionError as error:
|
||||
raise UpdateFailed(error) from error
|
||||
except AutarcoAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
except AutarcoConnectionError as err:
|
||||
raise UpdateFailed(err) from err
|
||||
return AutarcoData(
|
||||
solar=solar,
|
||||
inverters=inverters,
|
||||
|
||||
@@ -51,7 +51,7 @@ rules:
|
||||
This integration only polls data using a coordinator.
|
||||
Since the integration is read-only and poll-only (only provide sensor
|
||||
data), there is no need to implement parallel updates.
|
||||
reauthentication-flow: todo
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Connect to your Autarco account to get information about your solar panels.",
|
||||
"description": "Connect to your Autarco account, to get information about your sites.",
|
||||
"data": {
|
||||
"email": "[%key:common::config_flow::data::email%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
@@ -11,6 +11,16 @@
|
||||
"email": "The email address of your Autarco account.",
|
||||
"password": "The password of your Autarco account."
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "The password for {email} is no longer valid.",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "[%key:component::autarco::config::step::user::data_description::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -18,7 +28,8 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
||||
@@ -40,7 +40,6 @@ class BAFAutoComfort(BAFEntity, ClimateEntity):
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.FAN_ONLY]
|
||||
_attr_translation_key = "auto_comfort"
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
|
||||
@@ -46,7 +46,7 @@ class BAFFan(BAFEntity, FanEntity):
|
||||
| FanEntityFeature.TURN_OFF
|
||||
| FanEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
_attr_preset_modes = [PRESET_MODE_AUTO]
|
||||
_attr_speed_count = SPEED_COUNT
|
||||
_attr_name = None
|
||||
|
||||
@@ -8,16 +8,12 @@ from aiobafi6 import Device, OffOnAuto
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.color import (
|
||||
color_temperature_kelvin_to_mired,
|
||||
color_temperature_mired_to_kelvin,
|
||||
)
|
||||
|
||||
from . import BAFConfigEntry
|
||||
from .entity import BAFEntity
|
||||
@@ -77,25 +73,17 @@ class BAFStandaloneLight(BAFLight):
|
||||
def __init__(self, device: Device) -> None:
|
||||
"""Init a standalone light."""
|
||||
super().__init__(device)
|
||||
self._attr_min_mireds = color_temperature_kelvin_to_mired(
|
||||
device.light_warmest_color_temperature
|
||||
)
|
||||
self._attr_max_mireds = color_temperature_kelvin_to_mired(
|
||||
device.light_coolest_color_temperature
|
||||
)
|
||||
self._attr_max_color_temp_kelvin = device.light_warmest_color_temperature
|
||||
self._attr_min_color_temp_kelvin = device.light_coolest_color_temperature
|
||||
|
||||
@callback
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update attrs from device."""
|
||||
super()._async_update_attrs()
|
||||
self._attr_color_temp = color_temperature_kelvin_to_mired(
|
||||
self._device.light_color_temperature
|
||||
)
|
||||
self._attr_color_temp_kelvin = self._device.light_color_temperature
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
if (color_temp := kwargs.get(ATTR_COLOR_TEMP)) is not None:
|
||||
self._device.light_color_temperature = color_temperature_mired_to_kelvin(
|
||||
color_temp
|
||||
)
|
||||
if (color_temp := kwargs.get(ATTR_COLOR_TEMP_KELVIN)) is not None:
|
||||
self._device.light_color_temperature = color_temp
|
||||
await super().async_turn_on(**kwargs)
|
||||
|
||||
@@ -65,7 +65,6 @@ class BalboaClimateEntity(BalboaEntity, ClimateEntity):
|
||||
)
|
||||
_attr_translation_key = DOMAIN
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, client: SpaClient) -> None:
|
||||
"""Initialize the climate entity."""
|
||||
|
||||
@@ -38,7 +38,7 @@ class BalboaPumpFanEntity(BalboaEntity, FanEntity):
|
||||
| FanEntityFeature.TURN_OFF
|
||||
| FanEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
_attr_translation_key = "pump"
|
||||
|
||||
def __init__(self, control: SpaControl) -> None:
|
||||
|
||||
@@ -57,7 +57,6 @@ class BleBoxClimateEntity(BleBoxEntity[blebox_uniapi.climate.Climate], ClimateEn
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
|
||||
@@ -11,7 +11,7 @@ from blebox_uniapi.light import BleboxColorMode
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
@@ -22,6 +22,7 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from . import BleBoxConfigEntry
|
||||
from .entity import BleBoxEntity
|
||||
@@ -58,8 +59,8 @@ COLOR_MODE_MAP = {
|
||||
class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
"""Representation of BleBox lights."""
|
||||
|
||||
_attr_max_mireds = 370 # 1,000,000 divided by 2700 Kelvin = 370 Mireds
|
||||
_attr_min_mireds = 154 # 1,000,000 divided by 6500 Kelvin = 154 Mireds
|
||||
_attr_min_color_temp_kelvin = 2700 # 370 Mireds
|
||||
_attr_max_color_temp_kelvin = 6500 # 154 Mireds
|
||||
|
||||
def __init__(self, feature: blebox_uniapi.light.Light) -> None:
|
||||
"""Initialize a BleBox light."""
|
||||
@@ -78,9 +79,9 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
return self._feature.brightness
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return color temperature."""
|
||||
return self._feature.color_temp
|
||||
def color_temp_kelvin(self) -> int:
|
||||
"""Return the color temperature value in Kelvin."""
|
||||
return color_util.color_temperature_mired_to_kelvin(self._feature.color_temp)
|
||||
|
||||
@property
|
||||
def color_mode(self):
|
||||
@@ -136,7 +137,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
rgbw = kwargs.get(ATTR_RGBW_COLOR)
|
||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||
effect = kwargs.get(ATTR_EFFECT)
|
||||
color_temp = kwargs.get(ATTR_COLOR_TEMP)
|
||||
color_temp_kelvin = kwargs.get(ATTR_COLOR_TEMP_KELVIN)
|
||||
rgbww = kwargs.get(ATTR_RGBWW_COLOR)
|
||||
feature = self._feature
|
||||
value = feature.sensible_on_value
|
||||
@@ -144,9 +145,10 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
|
||||
if rgbw is not None:
|
||||
value = list(rgbw)
|
||||
if color_temp is not None:
|
||||
if color_temp_kelvin is not None:
|
||||
value = feature.return_color_temp_with_brightness(
|
||||
int(color_temp), self.brightness
|
||||
int(color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)),
|
||||
self.brightness,
|
||||
)
|
||||
|
||||
if rgbww is not None:
|
||||
@@ -158,9 +160,12 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
value = list(rgb)
|
||||
|
||||
if brightness is not None:
|
||||
if self.color_mode == ATTR_COLOR_TEMP:
|
||||
if self.color_mode == ColorMode.COLOR_TEMP:
|
||||
value = feature.return_color_temp_with_brightness(
|
||||
self.color_temp, brightness
|
||||
color_util.color_temperature_kelvin_to_mired(
|
||||
self.color_temp_kelvin
|
||||
),
|
||||
brightness,
|
||||
)
|
||||
else:
|
||||
value = feature.apply_brightness(value, brightness)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@@ -18,7 +17,7 @@ from homeassistant.helpers import (
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import ATTR_VIN, CONF_READ_ONLY, DOMAIN
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .coordinator import BMWConfigEntry, BMWDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,16 +48,6 @@ PLATFORMS = [
|
||||
SERVICE_UPDATE_STATE = "update_state"
|
||||
|
||||
|
||||
type BMWConfigEntry = ConfigEntry[BMWData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BMWData:
|
||||
"""Class to store BMW runtime data."""
|
||||
|
||||
coordinator: BMWDataUpdateCoordinator
|
||||
|
||||
|
||||
@callback
|
||||
def _async_migrate_options_from_data_if_missing(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
@@ -137,11 +126,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# Set up one data coordinator per account/config entry
|
||||
coordinator = BMWDataUpdateCoordinator(
|
||||
hass,
|
||||
entry=entry,
|
||||
config_entry=entry,
|
||||
)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = BMWData(coordinator)
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
# Set up all platforms except notify
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
|
||||
@@ -26,6 +26,8 @@ from .const import UNIT_MAP
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -201,7 +203,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the BMW binary sensors from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities = [
|
||||
BMWBinarySensor(coordinator, vehicle, description, hass.config.units)
|
||||
|
||||
@@ -22,6 +22,8 @@ from .entity import BMWBaseEntity
|
||||
if TYPE_CHECKING:
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -71,7 +73,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the BMW buttons from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[BMWButton] = []
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ DATA_SCHEMA = vol.Schema(
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
RECONFIGURE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
CAPTCHA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_CAPTCHA_TOKEN): str,
|
||||
@@ -111,9 +117,8 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
unique_id = f"{user_input[CONF_REGION]}-{user_input[CONF_USERNAME]}"
|
||||
await self.async_set_unique_id(unique_id)
|
||||
|
||||
if self.source in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
|
||||
self._abort_if_unique_id_mismatch(reason="account_mismatch")
|
||||
else:
|
||||
# Unique ID cannot change for reauth/reconfigure
|
||||
if self.source not in {SOURCE_REAUTH, SOURCE_RECONFIGURE}:
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
# Store user input for later use
|
||||
@@ -166,19 +171,39 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
|
||||
|
||||
async def async_step_change_password(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Show the change password step."""
|
||||
existing_data = (
|
||||
dict(self._existing_entry_data) if self._existing_entry_data else {}
|
||||
)
|
||||
|
||||
if user_input is not None:
|
||||
return await self.async_step_user(existing_data | user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="change_password",
|
||||
data_schema=RECONFIGURE_SCHEMA,
|
||||
description_placeholders={
|
||||
CONF_USERNAME: existing_data[CONF_USERNAME],
|
||||
CONF_REGION: existing_data[CONF_REGION],
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle configuration by re-auth."""
|
||||
self._existing_entry_data = entry_data
|
||||
return await self.async_step_user()
|
||||
return await self.async_step_change_password()
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a reconfiguration flow initialized by the user."""
|
||||
self._existing_entry_data = self._get_reconfigure_entry().data
|
||||
return await self.async_step_user()
|
||||
return await self.async_step_change_password()
|
||||
|
||||
async def async_step_captcha(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
||||
@@ -27,34 +27,40 @@ from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_I
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
type BMWConfigEntry = ConfigEntry[BMWDataUpdateCoordinator]
|
||||
|
||||
|
||||
class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Class to manage fetching BMW data."""
|
||||
|
||||
account: MyBMWAccount
|
||||
config_entry: BMWConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, *, entry: ConfigEntry) -> None:
|
||||
def __init__(self, hass: HomeAssistant, *, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize account-wide BMW data updater."""
|
||||
self.account = MyBMWAccount(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
get_region_from_name(entry.data[CONF_REGION]),
|
||||
config_entry.data[CONF_USERNAME],
|
||||
config_entry.data[CONF_PASSWORD],
|
||||
get_region_from_name(config_entry.data[CONF_REGION]),
|
||||
observer_position=GPSPosition(hass.config.latitude, hass.config.longitude),
|
||||
verify=get_default_context(),
|
||||
)
|
||||
self.read_only = entry.options[CONF_READ_ONLY]
|
||||
self._entry = entry
|
||||
self.read_only: bool = config_entry.options[CONF_READ_ONLY]
|
||||
|
||||
if CONF_REFRESH_TOKEN in entry.data:
|
||||
if CONF_REFRESH_TOKEN in config_entry.data:
|
||||
self.account.set_refresh_token(
|
||||
refresh_token=entry.data[CONF_REFRESH_TOKEN],
|
||||
gcid=entry.data.get(CONF_GCID),
|
||||
refresh_token=config_entry.data[CONF_REFRESH_TOKEN],
|
||||
gcid=config_entry.data.get(CONF_GCID),
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{DOMAIN}-{entry.data['username']}",
|
||||
update_interval=timedelta(seconds=SCAN_INTERVALS[entry.data[CONF_REGION]]),
|
||||
config_entry=config_entry,
|
||||
name=f"{DOMAIN}-{config_entry.data[CONF_USERNAME]}",
|
||||
update_interval=timedelta(
|
||||
seconds=SCAN_INTERVALS[config_entry.data[CONF_REGION]]
|
||||
),
|
||||
)
|
||||
|
||||
# Default to false on init so _async_update_data logic works
|
||||
@@ -88,9 +94,9 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
|
||||
"""Update or delete the refresh_token in the Config Entry."""
|
||||
data = {
|
||||
**self._entry.data,
|
||||
**self.config_entry.data,
|
||||
CONF_REFRESH_TOKEN: refresh_token,
|
||||
}
|
||||
if not refresh_token:
|
||||
data.pop(CONF_REFRESH_TOKEN)
|
||||
self.hass.config_entries.async_update_entry(self._entry, data=data)
|
||||
self.hass.config_entries.async_update_entry(self.config_entry, data=data)
|
||||
|
||||
@@ -16,6 +16,8 @@ from .const import ATTR_DIRECTION
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -25,7 +27,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW tracker from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
entities: list[BMWDeviceTracker] = []
|
||||
|
||||
for vehicle in coordinator.account.vehicles:
|
||||
|
||||
@@ -16,6 +16,8 @@ from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from . import BMWConfigEntry
|
||||
from .const import CONF_REFRESH_TOKEN
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
|
||||
@@ -49,7 +51,7 @@ async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: BMWConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
coordinator.account.config.log_responses = True
|
||||
await coordinator.account.get_vehicles(force_init=True)
|
||||
@@ -75,7 +77,7 @@ async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, config_entry: BMWConfigEntry, device: DeviceEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a device."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
coordinator.account.config.log_responses = True
|
||||
await coordinator.account.get_vehicles(force_init=True)
|
||||
|
||||
@@ -18,7 +18,10 @@ from . import BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
DOOR_LOCK_STATE = "door_lock_state"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,7 +31,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW lock from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
if not coordinator.read_only:
|
||||
async_add_entities(
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bimmer_connected"],
|
||||
"requirements": ["bimmer-connected[china]==0.17.0"]
|
||||
"requirements": ["bimmer-connected[china]==0.17.2"]
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
ATTR_LOCATION_ATTRIBUTES = ["street", "city", "postal_code", "country"]
|
||||
|
||||
POI_SCHEMA = vol.Schema(
|
||||
@@ -51,7 +53,7 @@ def get_service(
|
||||
targets = {}
|
||||
if (
|
||||
config_entry
|
||||
and (coordinator := config_entry.runtime_data.coordinator)
|
||||
and (coordinator := config_entry.runtime_data)
|
||||
and not coordinator.read_only
|
||||
):
|
||||
targets.update({v.name: v for v in coordinator.account.vehicles})
|
||||
|
||||
@@ -22,6 +22,8 @@ from . import BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -59,7 +61,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW number from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[BMWNumber] = []
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ from . import BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -66,7 +68,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW lock from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[BMWSelect] = []
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ from . import BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -191,7 +193,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW sensors from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities = [
|
||||
BMWSensor(coordinator, vehicle, description)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Enter your MyBMW/MINI Connected credentials.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
@@ -17,6 +18,12 @@
|
||||
"data_description": {
|
||||
"captcha_token": "One-time token retrieved from the captcha challenge."
|
||||
}
|
||||
},
|
||||
"change_password": {
|
||||
"description": "Update your MyBMW/MINI Connected password for account `{username}` in region `{region}`.",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -27,8 +34,7 @@
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"account_mismatch": "Username and region are not allowed to change"
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
||||
@@ -18,6 +18,8 @@ from . import BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -67,7 +69,7 @@ async def async_setup_entry(
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the MyBMW switch from config entry."""
|
||||
coordinator = config_entry.runtime_data.coordinator
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[BMWSwitch] = []
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ class BringConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
if user_input is not None:
|
||||
if not (errors := await self.validate_input(user_input)):
|
||||
self._abort_if_unique_id_mismatch()
|
||||
return self.async_update_reload_and_abort(
|
||||
self.reauth_entry, data=user_input
|
||||
)
|
||||
|
||||
72
homeassistant/components/bring/quality_scale.yaml
Normal file
72
homeassistant/components/bring/quality_scale.yaml
Normal file
@@ -0,0 +1,72 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: Only entity services
|
||||
appropriate-polling: done
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-high-level-description: todo
|
||||
docs-installation-instructions: todo
|
||||
docs-removal-instructions: todo
|
||||
entity-event-setup:
|
||||
status: exempt
|
||||
comment: The integration registers no events
|
||||
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: todo
|
||||
docs-installation-parameters: todo
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable:
|
||||
status: done
|
||||
comment: handled by coordinator
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: Integration is a service and has no devices.
|
||||
discovery:
|
||||
status: exempt
|
||||
comment: Integration is a service and has no devices.
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
docs-supported-devices: todo
|
||||
docs-supported-functions: todo
|
||||
docs-troubleshooting: todo
|
||||
docs-use-cases: todo
|
||||
dynamic-devices: todo
|
||||
entity-category: done
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: done
|
||||
exception-translations: todo
|
||||
icon-translations: done
|
||||
reconfiguration-flow: todo
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: |
|
||||
no repairs
|
||||
stale-devices: todo
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: todo
|
||||
@@ -24,6 +24,8 @@ from .coordinator import BringData, BringDataUpdateCoordinator
|
||||
from .entity import BringBaseEntity
|
||||
from .util import list_language, sum_attributes
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BringSensorEntityDescription(SensorEntityDescription):
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"unique_id_mismatch": "The login details correspond to a different account. Please re-authenticate to the previously configured account."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
||||
@@ -34,6 +34,8 @@ from .const import (
|
||||
from .coordinator import BringData, BringDataUpdateCoordinator
|
||||
from .entity import BringBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -52,7 +52,6 @@ class BroadlinkThermostat(BroadlinkEntity, ClimateEntity):
|
||||
)
|
||||
_attr_target_temperature_step = PRECISION_HALVES
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device: BroadlinkDevice) -> None:
|
||||
"""Initialize the climate entity."""
|
||||
|
||||
@@ -77,7 +77,6 @@ class BryantEvolutionClimate(ClimateEntity):
|
||||
HVACMode.OFF,
|
||||
]
|
||||
_attr_fan_modes = ["auto", "low", "med", "high"]
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -65,7 +65,6 @@ class BSBLANClimate(BSBLanEntity, ClimateEntity):
|
||||
|
||||
_attr_preset_modes = PRESET_MODES
|
||||
_attr_hvac_modes = HVAC_MODES
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -742,6 +742,7 @@ class BrSensor(SensorEntity):
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
self._data: BrData | None = None
|
||||
self._measured = None
|
||||
self._attr_unique_id = (
|
||||
f"{coordinates[CONF_LATITUDE]:2.6f}{coordinates[CONF_LONGITUDE]:2.6f}"
|
||||
@@ -756,17 +757,29 @@ class BrSensor(SensorEntity):
|
||||
if description.key.startswith(PRECIPITATION_FORECAST):
|
||||
self._timeframe = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity being added to hass."""
|
||||
if self._data is None:
|
||||
return
|
||||
self._update()
|
||||
|
||||
@callback
|
||||
def data_updated(self, data: BrData):
|
||||
"""Update data."""
|
||||
if self._load_data(data.data) and self.hass:
|
||||
"""Handle data update."""
|
||||
self._data = data
|
||||
if not self.hass:
|
||||
return
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
"""Update sensor data."""
|
||||
_LOGGER.debug("Updating sensor %s", self.entity_id)
|
||||
if self._load_data(self._data.data):
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
def _load_data(self, data): # noqa: C901
|
||||
"""Load the sensor with relevant data."""
|
||||
# Find sensor
|
||||
|
||||
# Check if we have a new measurement,
|
||||
# otherwise we do not have to update the sensor
|
||||
if self._measured == data.get(MEASURED):
|
||||
|
||||
@@ -7,12 +7,18 @@ from aiostreammagic import StreamMagicClient
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_RECONFIGURE,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import CONNECT_TIMEOUT, DOMAIN, STREAM_MAGIC_EXCEPTIONS
|
||||
|
||||
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
|
||||
|
||||
|
||||
class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Cambridge Audio configuration flow."""
|
||||
@@ -64,6 +70,17 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_reconfigure(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reconfiguration of the integration."""
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
step_id="reconfigure",
|
||||
data_schema=DATA_SCHEMA,
|
||||
)
|
||||
return await self.async_step_user(user_input)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -82,6 +99,12 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await self.async_set_unique_id(
|
||||
client.info.unit_id, raise_on_progress=False
|
||||
)
|
||||
if self.source == SOURCE_RECONFIGURE:
|
||||
self._abort_if_unique_id_mismatch(reason="wrong_device")
|
||||
return self.async_update_reload_and_abort(
|
||||
self._get_reconfigure_entry(),
|
||||
data_updates={CONF_HOST: user_input[CONF_HOST]},
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=client.info.name,
|
||||
@@ -91,6 +114,6 @@ class CambridgeAudioConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
await client.disconnect()
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
|
||||
data_schema=DATA_SCHEMA,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiostreammagic"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiostreammagic==2.10.0"],
|
||||
"zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ from homeassistant.components.media_player import (
|
||||
MediaType,
|
||||
RepeatMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import CambridgeAudioConfigEntry
|
||||
from .const import (
|
||||
CAMBRIDGE_MEDIA_TYPE_AIRABLE,
|
||||
CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO,
|
||||
@@ -62,7 +62,7 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: CambridgeAudioConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Cambridge Audio device based on a config entry."""
|
||||
|
||||
80
homeassistant/components/cambridge_audio/quality_scale.yaml
Normal file
80
homeassistant/components/cambridge_audio/quality_scale.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
rules:
|
||||
# Bronze
|
||||
action-setup:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not provide additional actions beyond play media which is setup by the media player entity.
|
||||
appropriate-polling:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration uses a push API. No polling required.
|
||||
brands: done
|
||||
common-modules: done
|
||||
config-flow-test-coverage: done
|
||||
config-flow: done
|
||||
dependency-transparency: done
|
||||
docs-actions: done
|
||||
docs-high-level-description: done
|
||||
docs-installation-instructions: done
|
||||
docs-removal-instructions: done
|
||||
entity-event-setup: done
|
||||
entity-unique-id: done
|
||||
has-entity-name: done
|
||||
runtime-data: done
|
||||
test-before-configure: done
|
||||
test-before-setup: done
|
||||
unique-config-entry: done
|
||||
|
||||
# Silver
|
||||
config-entry-unloading: done
|
||||
log-when-unavailable: done
|
||||
entity-unavailable: done
|
||||
action-exceptions: done
|
||||
reauthentication-flow:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not require authentication.
|
||||
parallel-updates: done
|
||||
test-coverage: done
|
||||
integration-owner: done
|
||||
docs-installation-parameters: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration does not have an options flow.
|
||||
# Gold
|
||||
entity-translations: done
|
||||
entity-device-class: done
|
||||
devices: done
|
||||
entity-category: done
|
||||
entity-disabled-by-default: done
|
||||
discovery: done
|
||||
stale-devices:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration is not a hub and as such only represents a single device.
|
||||
diagnostics: done
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
dynamic-devices:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration is not a hub and only represents a single device.
|
||||
discovery-update-info: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: |
|
||||
This integration doesn't have any cases where raising an issue is needed.
|
||||
docs-use-cases: done
|
||||
docs-supported-devices: done
|
||||
docs-supported-functions: done
|
||||
docs-data-update: done
|
||||
docs-known-limitations: done
|
||||
docs-troubleshooting: done
|
||||
docs-examples: done
|
||||
|
||||
# Platinum
|
||||
async-dependency: done
|
||||
inject-websession: done
|
||||
strict-typing: done
|
||||
@@ -7,11 +7,11 @@ from aiostreammagic import StreamMagicClient
|
||||
from aiostreammagic.models import DisplayBrightness
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import CambridgeAudioConfigEntry
|
||||
from .entity import CambridgeAudioEntity, command
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -81,7 +81,7 @@ CONTROL_ENTITIES: tuple[CambridgeAudioSelectEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: CambridgeAudioConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Cambridge Audio select entities based on a config entry."""
|
||||
|
||||
@@ -13,12 +13,23 @@
|
||||
},
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to setup {name}?"
|
||||
},
|
||||
"reconfigure": {
|
||||
"description": "Reconfigure your Cambridge Audio Streamer.",
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "[%key:component::cambridge_audio::config::step::user::data_description::host%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Failed to connect to Cambridge Audio device. Please make sure the device is powered up and connected to the network. Try power-cycling the device if it does not connect."
|
||||
},
|
||||
"abort": {
|
||||
"wrong_device": "This Cambridge Audio device does not match the existing device id. Please make sure you entered the correct IP address.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ from typing import Any
|
||||
from aiostreammagic import StreamMagicClient
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import CambridgeAudioConfigEntry
|
||||
from .entity import CambridgeAudioEntity, command
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@@ -45,7 +45,7 @@ CONTROL_ENTITIES: tuple[CambridgeAudioSwitchEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: CambridgeAudioConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Cambridge Audio switch entities based on a config entry."""
|
||||
|
||||
@@ -516,19 +516,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> CameraEntityFeature:
|
||||
"""Return the supported features as CameraEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CameraEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@cached_property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return true if the device is recording."""
|
||||
@@ -582,7 +569,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
self._deprecate_attr_frontend_stream_type_logged = True
|
||||
return self._attr_frontend_stream_type
|
||||
if CameraEntityFeature.STREAM not in self.supported_features_compat:
|
||||
if CameraEntityFeature.STREAM not in self.supported_features:
|
||||
return None
|
||||
if (
|
||||
self._webrtc_provider
|
||||
@@ -811,9 +798,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
self.__supports_stream = (
|
||||
self.supported_features_compat & CameraEntityFeature.STREAM
|
||||
)
|
||||
self.__supports_stream = self.supported_features & CameraEntityFeature.STREAM
|
||||
await self.async_refresh_providers(write_state=False)
|
||||
|
||||
async def async_refresh_providers(self, *, write_state: bool = True) -> None:
|
||||
@@ -853,7 +838,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
|
||||
) -> _T | None:
|
||||
"""Get first provider that supports this camera."""
|
||||
if CameraEntityFeature.STREAM not in self.supported_features_compat:
|
||||
if CameraEntityFeature.STREAM not in self.supported_features:
|
||||
return None
|
||||
|
||||
return await fn(self.hass, self)
|
||||
@@ -911,7 +896,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def camera_capabilities(self) -> CameraCapabilities:
|
||||
"""Return the camera capabilities."""
|
||||
frontend_stream_types = set()
|
||||
if CameraEntityFeature.STREAM in self.supported_features_compat:
|
||||
if CameraEntityFeature.STREAM in self.supported_features:
|
||||
if self._supports_native_sync_webrtc or self._supports_native_async_webrtc:
|
||||
# The camera has a native WebRTC implementation
|
||||
frontend_stream_types.add(StreamType.WEB_RTC)
|
||||
@@ -931,8 +916,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""
|
||||
super().async_write_ha_state()
|
||||
if self.__supports_stream != (
|
||||
supports_stream := self.supported_features_compat
|
||||
& CameraEntityFeature.STREAM
|
||||
supports_stream := self.supported_features & CameraEntityFeature.STREAM
|
||||
):
|
||||
self.__supports_stream = supports_stream
|
||||
self._invalidate_camera_capabilities_cache()
|
||||
|
||||
@@ -70,7 +70,6 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self, ac_host: str, ac_index: int, coordinator: CCM15Coordinator
|
||||
|
||||
@@ -74,7 +74,7 @@ class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
title=title,
|
||||
data={CONF_HOST: host, CONF_PORT: port},
|
||||
)
|
||||
if self.context["source"] == SOURCE_IMPORT:
|
||||
if self.source == SOURCE_IMPORT:
|
||||
_LOGGER.error("Config import failed for %s", user_input[CONF_HOST])
|
||||
return self.async_abort(reason="import_failed")
|
||||
else:
|
||||
@@ -94,10 +94,3 @@ class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Import a config entry.
|
||||
|
||||
Only host was required in the yaml file all other fields are optional
|
||||
"""
|
||||
return await self.async_step_user(import_data)
|
||||
|
||||
@@ -2,63 +2,18 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import CertExpiryConfigEntry
|
||||
from .const import DEFAULT_PORT, DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .coordinator import CertExpiryDataUpdateCoordinator
|
||||
from .entity import CertExpiryEntity
|
||||
|
||||
SCAN_INTERVAL = timedelta(hours=12)
|
||||
|
||||
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up certificate expiry sensor."""
|
||||
|
||||
@callback
|
||||
def schedule_import(_: Event) -> None:
|
||||
"""Schedule delayed import after HA is fully started."""
|
||||
async_call_later(hass, 10, do_import)
|
||||
|
||||
@callback
|
||||
def do_import(_: datetime) -> None:
|
||||
"""Process YAML import."""
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_IMPORT}, data=dict(config)
|
||||
)
|
||||
)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, schedule_import)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import functools as ft
|
||||
import logging
|
||||
@@ -28,7 +27,6 @@ from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.loader import async_get_issue_tracker, async_suggest_report_issue
|
||||
@@ -303,115 +301,6 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
__climate_reported_legacy_aux = False
|
||||
|
||||
__mod_supported_features: ClimateEntityFeature = ClimateEntityFeature(0)
|
||||
# Integrations should set `_enable_turn_on_off_backwards_compatibility` to False
|
||||
# once migrated and set the feature flags TURN_ON/TURN_OFF as needed.
|
||||
_enable_turn_on_off_backwards_compatibility: bool = True
|
||||
|
||||
def __getattribute__(self, name: str, /) -> Any:
|
||||
"""Get attribute.
|
||||
|
||||
Modify return of `supported_features` to
|
||||
include `_mod_supported_features` if attribute is set.
|
||||
"""
|
||||
if name != "supported_features":
|
||||
return super().__getattribute__(name)
|
||||
|
||||
# Convert the supported features to ClimateEntityFeature.
|
||||
# Remove this compatibility shim in 2025.1 or later.
|
||||
_supported_features: ClimateEntityFeature = super().__getattribute__(
|
||||
"supported_features"
|
||||
)
|
||||
_mod_supported_features: ClimateEntityFeature = super().__getattribute__(
|
||||
"_ClimateEntity__mod_supported_features"
|
||||
)
|
||||
if type(_supported_features) is int: # noqa: E721
|
||||
_features = ClimateEntityFeature(_supported_features)
|
||||
self._report_deprecated_supported_features_values(_features)
|
||||
else:
|
||||
_features = _supported_features
|
||||
|
||||
if not _mod_supported_features:
|
||||
return _features
|
||||
|
||||
# Add automatically calculated ClimateEntityFeature.TURN_OFF/TURN_ON to
|
||||
# supported features and return it
|
||||
return _features | _mod_supported_features
|
||||
|
||||
@callback
|
||||
def add_to_platform_start(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
platform: EntityPlatform,
|
||||
parallel_updates: asyncio.Semaphore | None,
|
||||
) -> None:
|
||||
"""Start adding an entity to a platform."""
|
||||
super().add_to_platform_start(hass, platform, parallel_updates)
|
||||
|
||||
def _report_turn_on_off(feature: str, method: str) -> None:
|
||||
"""Log warning not implemented turn on/off feature."""
|
||||
report_issue = self._suggest_report_issue()
|
||||
if feature.startswith("TURN"):
|
||||
message = (
|
||||
"Entity %s (%s) does not set ClimateEntityFeature.%s"
|
||||
" but implements the %s method. Please %s"
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"Entity %s (%s) implements HVACMode(s): %s and therefore implicitly"
|
||||
" supports the %s methods without setting the proper"
|
||||
" ClimateEntityFeature. Please %s"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
message,
|
||||
self.entity_id,
|
||||
type(self),
|
||||
feature,
|
||||
method,
|
||||
report_issue,
|
||||
)
|
||||
|
||||
# Adds ClimateEntityFeature.TURN_OFF/TURN_ON depending on service calls implemented
|
||||
# This should be removed in 2025.1.
|
||||
if self._enable_turn_on_off_backwards_compatibility is False:
|
||||
# Return if integration has migrated already
|
||||
return
|
||||
|
||||
supported_features = self.supported_features
|
||||
if supported_features & CHECK_TURN_ON_OFF_FEATURE_FLAG:
|
||||
# The entity supports both turn_on and turn_off, the backwards compatibility
|
||||
# checks are not needed
|
||||
return
|
||||
|
||||
if not supported_features & ClimateEntityFeature.TURN_OFF and (
|
||||
type(self).async_turn_off is not ClimateEntity.async_turn_off
|
||||
or type(self).turn_off is not ClimateEntity.turn_off
|
||||
):
|
||||
# turn_off implicitly supported by implementing turn_off method
|
||||
_report_turn_on_off("TURN_OFF", "turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
if not supported_features & ClimateEntityFeature.TURN_ON and (
|
||||
type(self).async_turn_on is not ClimateEntity.async_turn_on
|
||||
or type(self).turn_on is not ClimateEntity.turn_on
|
||||
):
|
||||
# turn_on implicitly supported by implementing turn_on method
|
||||
_report_turn_on_off("TURN_ON", "turn_on")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
|
||||
if (modes := self.hvac_modes) and len(modes) >= 2 and HVACMode.OFF in modes:
|
||||
# turn_on/off implicitly supported by including more modes than 1 and one of these
|
||||
# are HVACMode.OFF
|
||||
_modes = [_mode for _mode in modes if _mode is not None]
|
||||
_report_turn_on_off(", ".join(_modes or []), "turn_on/turn_off")
|
||||
self.__mod_supported_features |= ( # pylint: disable=unused-private-member
|
||||
ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF
|
||||
)
|
||||
|
||||
def _report_legacy_aux(self) -> None:
|
||||
"""Log warning and create an issue if the entity implements legacy auxiliary heater."""
|
||||
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.85.0"],
|
||||
"requirements": ["hass-nabucasa==0.86.0"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@
|
||||
},
|
||||
"services": {
|
||||
"remote_connect": {
|
||||
"name": "Remote connect",
|
||||
"description": "Makes the instance UI accessible from outside of the local network by using Home Assistant Cloud."
|
||||
"name": "Enable remote access",
|
||||
"description": "Makes the instance UI accessible from outside of the local network by enabling your Home Assistant Cloud connection."
|
||||
},
|
||||
"remote_disconnect": {
|
||||
"name": "Remote disconnect",
|
||||
"description": "Disconnects the Home Assistant UI from the Home Assistant Cloud. You will no longer be able to access your Home Assistant instance from outside your local network."
|
||||
"name": "Disable remote access",
|
||||
"description": "Disconnects the instance UI from Home Assistant Cloud. This disables access to it from outside your local network."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ class ComelitClimateEntity(CoordinatorEntity[ComelitSerialBridge], ClimateEntity
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -68,7 +68,7 @@ class ComfoConnectFan(FanEntity):
|
||||
| FanEntityFeature.TURN_OFF
|
||||
| FanEntityFeature.TURN_ON
|
||||
)
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
_attr_preset_modes = PRESET_MODES
|
||||
current_speed: float | None = None
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"codeowners": ["@gjohansson-ST"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/command_line",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["jsonpath==0.82.2"]
|
||||
}
|
||||
|
||||
@@ -163,7 +163,15 @@ def websocket_get_entities(
|
||||
# remove the entity from the category.
|
||||
vol.Optional("categories"): cv.schema_with_slug_keys(vol.Any(str, None)),
|
||||
vol.Optional("device_class"): vol.Any(str, None),
|
||||
vol.Optional("icon"): vol.Any(str, None),
|
||||
vol.Optional("icon"): vol.Any(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional("state"): dict,
|
||||
}
|
||||
),
|
||||
str,
|
||||
None,
|
||||
),
|
||||
vol.Optional("labels"): [str],
|
||||
vol.Optional("name"): vol.Any(str, None),
|
||||
vol.Optional("new_entity_id"): str,
|
||||
|
||||
@@ -70,7 +70,7 @@ _ENTITY_REGISTRY_UPDATE_FIELDS = ["aliases", "name", "original_name"]
|
||||
|
||||
REGEX_TYPE = type(re.compile(""))
|
||||
TRIGGER_CALLBACK_TYPE = Callable[
|
||||
[str, RecognizeResult, str | None], Awaitable[str | None]
|
||||
[ConversationInput, RecognizeResult], Awaitable[str | None]
|
||||
]
|
||||
METADATA_CUSTOM_SENTENCE = "hass_custom_sentence"
|
||||
METADATA_CUSTOM_FILE = "hass_custom_file"
|
||||
@@ -246,7 +246,7 @@ class DefaultAgent(ConversationEntity):
|
||||
self._unexposed_names_trie: Trie | None = None
|
||||
|
||||
# Sentences that will trigger a callback (skipping intent recognition)
|
||||
self._trigger_sentences: list[TriggerData] = []
|
||||
self.trigger_sentences: list[TriggerData] = []
|
||||
self._trigger_intents: Intents | None = None
|
||||
self._unsub_clear_slot_list: list[Callable[[], None]] | None = None
|
||||
self._load_intents_lock = asyncio.Lock()
|
||||
@@ -711,7 +711,7 @@ class DefaultAgent(ConversationEntity):
|
||||
for name_tuple in self._get_entity_name_tuples(exposed=False):
|
||||
self._unexposed_names_trie.insert(
|
||||
name_tuple[0].lower(),
|
||||
TextSlotValue.from_tuple(name_tuple),
|
||||
TextSlotValue.from_tuple(name_tuple, allow_template=False),
|
||||
)
|
||||
|
||||
# Build filtered slot list
|
||||
@@ -1188,7 +1188,7 @@ class DefaultAgent(ConversationEntity):
|
||||
) -> core.CALLBACK_TYPE:
|
||||
"""Register a list of sentences that will trigger a callback when recognized."""
|
||||
trigger_data = TriggerData(sentences=sentences, callback=callback)
|
||||
self._trigger_sentences.append(trigger_data)
|
||||
self.trigger_sentences.append(trigger_data)
|
||||
|
||||
# Force rebuild on next use
|
||||
self._trigger_intents = None
|
||||
@@ -1205,7 +1205,7 @@ class DefaultAgent(ConversationEntity):
|
||||
# This works because the intents are rebuilt on every
|
||||
# register/unregister.
|
||||
str(trigger_id): {"data": [{"sentences": trigger_data.sentences}]}
|
||||
for trigger_id, trigger_data in enumerate(self._trigger_sentences)
|
||||
for trigger_id, trigger_data in enumerate(self.trigger_sentences)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1228,7 +1228,7 @@ class DefaultAgent(ConversationEntity):
|
||||
@core.callback
|
||||
def _unregister_trigger(self, trigger_data: TriggerData) -> None:
|
||||
"""Unregister a set of trigger sentences."""
|
||||
self._trigger_sentences.remove(trigger_data)
|
||||
self.trigger_sentences.remove(trigger_data)
|
||||
|
||||
# Force rebuild on next use
|
||||
self._trigger_intents = None
|
||||
@@ -1241,7 +1241,7 @@ class DefaultAgent(ConversationEntity):
|
||||
Calls the registered callbacks if there's a match and returns a sentence
|
||||
trigger result.
|
||||
"""
|
||||
if not self._trigger_sentences:
|
||||
if not self.trigger_sentences:
|
||||
# No triggers registered
|
||||
return None
|
||||
|
||||
@@ -1286,9 +1286,7 @@ class DefaultAgent(ConversationEntity):
|
||||
|
||||
# Gather callback responses in parallel
|
||||
trigger_callbacks = [
|
||||
self._trigger_sentences[trigger_id].callback(
|
||||
user_input.text, trigger_result, user_input.device_id
|
||||
)
|
||||
self.trigger_sentences[trigger_id].callback(user_input, trigger_result)
|
||||
for trigger_id, trigger_result in result.matched_triggers.items()
|
||||
]
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ def async_setup(hass: HomeAssistant) -> None:
|
||||
websocket_api.async_register_command(hass, websocket_process)
|
||||
websocket_api.async_register_command(hass, websocket_prepare)
|
||||
websocket_api.async_register_command(hass, websocket_list_agents)
|
||||
websocket_api.async_register_command(hass, websocket_list_sentences)
|
||||
websocket_api.async_register_command(hass, websocket_hass_agent_debug)
|
||||
|
||||
|
||||
@@ -150,6 +151,27 @@ async def websocket_list_agents(
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {"agents": agents}))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "conversation/sentences/list",
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_list_sentences(
|
||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict
|
||||
) -> None:
|
||||
"""List custom registered sentences."""
|
||||
agent = hass.data.get(DATA_DEFAULT_ENTITY)
|
||||
assert isinstance(agent, DefaultAgent)
|
||||
|
||||
sentences = []
|
||||
for trigger_data in agent.trigger_sentences:
|
||||
sentences.extend(trigger_data.sentences)
|
||||
|
||||
connection.send_result(msg["id"], {"trigger_sentences": sentences})
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "conversation/agent/homeassistant/debug",
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==2.0.4", "home-assistant-intents==2024.11.27"]
|
||||
"requirements": ["hassil==2.0.5", "home-assistant-intents==2024.12.9"]
|
||||
}
|
||||
|
||||
@@ -40,6 +40,17 @@ class ConversationInput:
|
||||
agent_id: str | None = None
|
||||
"""Agent to use for processing."""
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""Return input as a dict."""
|
||||
return {
|
||||
"text": self.text,
|
||||
"context": self.context.as_dict(),
|
||||
"conversation_id": self.conversation_id,
|
||||
"device_id": self.device_id,
|
||||
"language": self.language,
|
||||
"agent_id": self.agent_id,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ConversationResult:
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import UNDEFINED, ConfigType
|
||||
|
||||
from .const import DATA_DEFAULT_ENTITY, DOMAIN
|
||||
from .models import ConversationInput
|
||||
|
||||
|
||||
def has_no_punctuation(value: list[str]) -> list[str]:
|
||||
@@ -62,7 +63,7 @@ async def async_attach_trigger(
|
||||
job = HassJob(action)
|
||||
|
||||
async def call_action(
|
||||
sentence: str, result: RecognizeResult, device_id: str | None
|
||||
user_input: ConversationInput, result: RecognizeResult
|
||||
) -> str | None:
|
||||
"""Call action with right context."""
|
||||
|
||||
@@ -83,12 +84,13 @@ async def async_attach_trigger(
|
||||
trigger_input: dict[str, Any] = { # Satisfy type checker
|
||||
**trigger_data,
|
||||
"platform": DOMAIN,
|
||||
"sentence": sentence,
|
||||
"sentence": user_input.text,
|
||||
"details": details,
|
||||
"slots": { # direct access to values
|
||||
entity_name: entity["value"] for entity_name, entity in details.items()
|
||||
},
|
||||
"device_id": device_id,
|
||||
"device_id": user_input.device_id,
|
||||
"user_input": user_input.as_dict(),
|
||||
}
|
||||
|
||||
# Wait for the automation to complete
|
||||
|
||||
@@ -55,7 +55,6 @@ class CoolmasterClimate(CoolmasterEntity, ClimateEntity):
|
||||
"""Representation of a coolmaster climate device."""
|
||||
|
||||
_attr_name = None
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator, unit_id, info, supported_modes):
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
"services": {
|
||||
"decrement": {
|
||||
"name": "Decrement",
|
||||
"description": "Decrements a counter."
|
||||
"description": "Decrements a counter by its step size."
|
||||
},
|
||||
"increment": {
|
||||
"name": "Increment",
|
||||
"description": "Increments a counter."
|
||||
"description": "Increments a counter by its step size."
|
||||
},
|
||||
"reset": {
|
||||
"name": "Reset",
|
||||
"description": "Resets a counter."
|
||||
"description": "Resets a counter to its initial value."
|
||||
},
|
||||
"set_value": {
|
||||
"name": "Set",
|
||||
"description": "Sets the counter value.",
|
||||
"description": "Sets the counter to a specific value.",
|
||||
"fields": {
|
||||
"value": {
|
||||
"name": "Value",
|
||||
|
||||
@@ -300,10 +300,6 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def supported_features(self) -> CoverEntityFeature:
|
||||
"""Flag supported features."""
|
||||
if (features := self._attr_supported_features) is not None:
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CoverEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
supported_features = (
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/cups",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["pycups==1.9.73"]
|
||||
"requirements": ["pycups==2.0.4"]
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ class DaikinClimate(DaikinEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_fan_modes: list[str]
|
||||
_attr_swing_modes: list[str]
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, coordinator: DaikinCoordinator) -> None:
|
||||
"""Initialize the climate device."""
|
||||
|
||||
@@ -4,8 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from pydeako.deako import Deako, DeviceListTimeout, FindDevicesTimeout
|
||||
from pydeako.discover import DeakoDiscoverer
|
||||
from pydeako import Deako, DeakoDiscoverer, FindDevicesError
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -30,12 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: DeakoConfigEntry) -> boo
|
||||
await connection.connect()
|
||||
try:
|
||||
await connection.find_devices()
|
||||
except DeviceListTimeout as exc: # device list never received
|
||||
_LOGGER.warning("Device not responding to device list")
|
||||
await connection.disconnect()
|
||||
raise ConfigEntryNotReady(exc) from exc
|
||||
except FindDevicesTimeout as exc: # total devices expected not received
|
||||
_LOGGER.warning("Device not responding to device requests")
|
||||
except FindDevicesError as exc:
|
||||
_LOGGER.warning("Error finding devices: %s", exc)
|
||||
await connection.disconnect()
|
||||
raise ConfigEntryNotReady(exc) from exc
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Config flow for deako."""
|
||||
|
||||
from pydeako.discover import DeakoDiscoverer, DevicesNotFoundException
|
||||
from pydeako import DeakoDiscoverer, DevicesNotFoundException
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydeako.deako import Deako
|
||||
from pydeako import Deako
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/deako",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydeako"],
|
||||
"requirements": ["pydeako==0.5.4"],
|
||||
"requirements": ["pydeako==0.6.0"],
|
||||
"single_config_entry": true,
|
||||
"zeroconf": ["_deako._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["debugpy==1.8.6"]
|
||||
"requirements": ["debugpy==1.8.8"]
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ class DeconzThermostat(DeconzDevice[Thermostat], ClimateEntity):
|
||||
TYPE = CLIMATE_DOMAIN
|
||||
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_enable_turn_on_off_backwards_compatibility = False
|
||||
|
||||
def __init__(self, device: Thermostat, hub: DeconzHub) -> None:
|
||||
"""Set up thermostat device."""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user