forked from home-assistant/core
Compare commits
449 Commits
cached_pro
...
2023.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a141bc08e7 | ||
|
|
a982958143 | ||
|
|
2822d98544 | ||
|
|
4f95039dfd | ||
|
|
658e87b6a5 | ||
|
|
e6c8e0460f | ||
|
|
c78628aa29 | ||
|
|
3dc1ceed0b | ||
|
|
89e737facb | ||
|
|
15ab483f61 | ||
|
|
0b3ff859e6 | ||
|
|
bdcc9ec984 | ||
|
|
382bfa24a8 | ||
|
|
6a54c18818 | ||
|
|
76ac7fa6a0 | ||
|
|
de1a367cff | ||
|
|
e493cd642c | ||
|
|
c2a41fc21e | ||
|
|
ebc123c355 | ||
|
|
4874e13af8 | ||
|
|
9969f67508 | ||
|
|
855962d729 | ||
|
|
98b8e27b08 | ||
|
|
b26e624b2b | ||
|
|
3c7ced21ad | ||
|
|
eec05d4fc8 | ||
|
|
32927e050f | ||
|
|
c6b7a3d564 | ||
|
|
6f5373fa6e | ||
|
|
7bd9933092 | ||
|
|
52f60f1e48 | ||
|
|
3667eb9400 | ||
|
|
80b24b23d3 | ||
|
|
816adce257 | ||
|
|
d18716e5f8 | ||
|
|
4096614ac0 | ||
|
|
ef31608ce0 | ||
|
|
7408fa4ab6 | ||
|
|
10b97a77c6 | ||
|
|
3540c78fb9 | ||
|
|
866e130967 | ||
|
|
d969b89a12 | ||
|
|
bca5aae3bb | ||
|
|
7a21e858ab | ||
|
|
224886eb29 | ||
|
|
95594a23dc | ||
|
|
4c10d186c0 | ||
|
|
6275932c29 | ||
|
|
4229778cf6 | ||
|
|
57369be322 | ||
|
|
3e19fba7d3 | ||
|
|
2196bd3a13 | ||
|
|
91ea14d9eb | ||
|
|
b90df4bdca | ||
|
|
025969a76f | ||
|
|
8ed7390a61 | ||
|
|
36f6c02c52 | ||
|
|
a709b6af4c | ||
|
|
284926159b | ||
|
|
8e2c0984b7 | ||
|
|
0bb0e3fe72 | ||
|
|
c123c22846 | ||
|
|
577ffef25c | ||
|
|
10a9b063fa | ||
|
|
e22b79dbd9 | ||
|
|
89c580f27d | ||
|
|
239cbb7ad1 | ||
|
|
d8f8557ae1 | ||
|
|
3df1e9bc99 | ||
|
|
8db987e0b3 | ||
|
|
d77a168121 | ||
|
|
3550a9e2d2 | ||
|
|
0ce3b89139 | ||
|
|
3c0d234332 | ||
|
|
601ca8562c | ||
|
|
2720e2fade | ||
|
|
d0e60f66fa | ||
|
|
806b0cb1b6 | ||
|
|
2e18641863 | ||
|
|
e4e5ecf9b4 | ||
|
|
1e66aaff0d | ||
|
|
ebb28973e0 | ||
|
|
c260e787aa | ||
|
|
fdd8489b93 | ||
|
|
a77fb14baf | ||
|
|
98242b5d54 | ||
|
|
d38aab1607 | ||
|
|
910aecb33b | ||
|
|
ca20663c31 | ||
|
|
327a54e65a | ||
|
|
e907585b7f | ||
|
|
3fa009b98c | ||
|
|
226f1d7c73 | ||
|
|
be49f90550 | ||
|
|
4993b6ee90 | ||
|
|
2de0b0f6ac | ||
|
|
313e15a915 | ||
|
|
11fd9ffa60 | ||
|
|
4192fdbdfd | ||
|
|
d1e8513d63 | ||
|
|
9a14d437dd | ||
|
|
19cbedcc05 | ||
|
|
8a6d54237f | ||
|
|
70d3dc2ef8 | ||
|
|
a8807f600b | ||
|
|
73cb17cbf5 | ||
|
|
26d171fc92 | ||
|
|
a4449d39f3 | ||
|
|
a9b8e0077d | ||
|
|
97d9876cf7 | ||
|
|
0e89d0a26b | ||
|
|
03e7170080 | ||
|
|
c67f37d1be | ||
|
|
6aba1a5af3 | ||
|
|
1cf472f4e3 | ||
|
|
b8fc6e0c66 | ||
|
|
a04c98a703 | ||
|
|
4255a2af15 | ||
|
|
c47a43c2b9 | ||
|
|
bcca1c91e6 | ||
|
|
9cd7034dbd | ||
|
|
ee72a952de | ||
|
|
22e32bc737 | ||
|
|
2a42622de9 | ||
|
|
8f88b710f0 | ||
|
|
1df12e8771 | ||
|
|
129fee09d3 | ||
|
|
61ab84bf04 | ||
|
|
4f99a71f61 | ||
|
|
f165357772 | ||
|
|
c156951925 | ||
|
|
612f33e372 | ||
|
|
2b274b4e95 | ||
|
|
2ac5bb46b1 | ||
|
|
c4a46294cc | ||
|
|
cda5ee5814 | ||
|
|
698333f894 | ||
|
|
b93ceca804 | ||
|
|
286cff314a | ||
|
|
d44ef07082 | ||
|
|
8dd2e21d0b | ||
|
|
f55ada5d61 | ||
|
|
1f72a5b1fe | ||
|
|
71b192c072 | ||
|
|
17c64ed791 | ||
|
|
4e940df224 | ||
|
|
a23c0e12f1 | ||
|
|
be22195cf4 | ||
|
|
a39ef03ff5 | ||
|
|
1687ff1f28 | ||
|
|
28a5e4735f | ||
|
|
25dc9f5942 | ||
|
|
03dac6e171 | ||
|
|
804b27cc2f | ||
|
|
b64be798df | ||
|
|
79f1c86789 | ||
|
|
ecfb259438 | ||
|
|
4959fce1e0 | ||
|
|
3d84c6e21c | ||
|
|
d8d580ad58 | ||
|
|
e0d1d16da1 | ||
|
|
b2c0ca304b | ||
|
|
de4e3b5ffe | ||
|
|
c14a2dd912 | ||
|
|
acb7b1fe3b | ||
|
|
4393aa4f50 | ||
|
|
0a74bffe67 | ||
|
|
489781c1e6 | ||
|
|
24ea865553 | ||
|
|
0856121046 | ||
|
|
e9495c9cc6 | ||
|
|
6c2c4c989f | ||
|
|
103375ef95 | ||
|
|
ff015310fd | ||
|
|
c1953b0ae4 | ||
|
|
ee4459f41e | ||
|
|
c2cd4d0517 | ||
|
|
796b5ef196 | ||
|
|
ae21ab2945 | ||
|
|
8b6ed9c6b9 | ||
|
|
fe7857c8ec | ||
|
|
0bfb81ecf3 | ||
|
|
36b0fc17df | ||
|
|
8b563120b5 | ||
|
|
d430e089d2 | ||
|
|
149ac4f99e | ||
|
|
a5b91cb7e3 | ||
|
|
2747da784c | ||
|
|
98a94fea99 | ||
|
|
f6aae8b01b | ||
|
|
265ccae54f | ||
|
|
cfb133d431 | ||
|
|
ebd5eb4470 | ||
|
|
f5b2273fc1 | ||
|
|
5955be46a4 | ||
|
|
7a82176670 | ||
|
|
d2cd0c9934 | ||
|
|
04e277ac95 | ||
|
|
72806bfaf2 | ||
|
|
b53b162d05 | ||
|
|
4daacf9c4b | ||
|
|
aaa4ee79b8 | ||
|
|
4e05205174 | ||
|
|
ec8988f8ea | ||
|
|
21c619e702 | ||
|
|
bafb81337b | ||
|
|
7fa86d3998 | ||
|
|
78380c0cd4 | ||
|
|
ed2daf1f65 | ||
|
|
a885ceefa2 | ||
|
|
a4a6972237 | ||
|
|
34827571ba | ||
|
|
16fe79db75 | ||
|
|
dd3693caca | ||
|
|
286bdff8bc | ||
|
|
e7f29263e3 | ||
|
|
99a622c8ee | ||
|
|
179e1da164 | ||
|
|
d7b7deb95f | ||
|
|
bbae2061e7 | ||
|
|
6a85e227db | ||
|
|
48776f86dc | ||
|
|
0fc51ac75a | ||
|
|
f1a54a510c | ||
|
|
2092bd225d | ||
|
|
e18f7dffb0 | ||
|
|
e8cb9fba7b | ||
|
|
243abf32c0 | ||
|
|
8b25fd8563 | ||
|
|
8d6a711cac | ||
|
|
1dc9c77a3e | ||
|
|
e4eb7e4796 | ||
|
|
09bfb08067 | ||
|
|
0b32a6e0d1 | ||
|
|
b0834472bc | ||
|
|
68db751ce1 | ||
|
|
1a8bc1930c | ||
|
|
4b9bfe9a50 | ||
|
|
a8b2c1edfa | ||
|
|
bff4b0c79c | ||
|
|
5a90a44233 | ||
|
|
1fec407a24 | ||
|
|
17ac1a6d32 | ||
|
|
5c4d010b90 | ||
|
|
8bbb396048 | ||
|
|
db01aecb02 | ||
|
|
cd26de73b4 | ||
|
|
3e85a29b86 | ||
|
|
7c676c0a7d | ||
|
|
43fe30f6ee | ||
|
|
e7cc839a96 | ||
|
|
df65fa3899 | ||
|
|
30fcfaf1ec | ||
|
|
cb22fb16f8 | ||
|
|
e19b29d6ae | ||
|
|
90854df5b2 | ||
|
|
96bf8ef8d6 | ||
|
|
f3fc741a71 | ||
|
|
190d67b56c | ||
|
|
116dd67472 | ||
|
|
878d41a472 | ||
|
|
50e36fbdda | ||
|
|
071d3a474f | ||
|
|
fe28067481 | ||
|
|
9ca0a095ab | ||
|
|
c1e8eb7c96 | ||
|
|
e9eb366f3b | ||
|
|
0c66ccebd1 | ||
|
|
723f6d35b0 | ||
|
|
968bc25259 | ||
|
|
f61332c9b4 | ||
|
|
d8e73b6a6b | ||
|
|
4d2fa5bdbc | ||
|
|
2c9213baa1 | ||
|
|
4d05a3ae79 | ||
|
|
a80862f3db | ||
|
|
c225c46853 | ||
|
|
3c015f85f4 | ||
|
|
4d4e7522f4 | ||
|
|
90c1263501 | ||
|
|
39f76b757d | ||
|
|
9de2b6c253 | ||
|
|
58f8f9c82a | ||
|
|
22f29e8c84 | ||
|
|
af7b25d748 | ||
|
|
30d22fe49b | ||
|
|
b62080cb78 | ||
|
|
ba244b7af7 | ||
|
|
b12109dcde | ||
|
|
10f116a507 | ||
|
|
16bcbe3d22 | ||
|
|
4ab8411145 | ||
|
|
7add36d847 | ||
|
|
c3d02d68b7 | ||
|
|
c2f7e5840b | ||
|
|
8d1cd39044 | ||
|
|
73bc5a4e8f | ||
|
|
98cc45ec10 | ||
|
|
363dab7ce4 | ||
|
|
2c3a50fdb1 | ||
|
|
6d47feb595 | ||
|
|
5f14cdf69d | ||
|
|
51aa2ba835 | ||
|
|
9dc586bd98 | ||
|
|
c2457b8574 | ||
|
|
0af71851a4 | ||
|
|
d6cd5648b9 | ||
|
|
c4288e7b1f | ||
|
|
398dbed72e | ||
|
|
9fe24c54e9 | ||
|
|
dbe4252d34 | ||
|
|
185936deda | ||
|
|
b70a67404b | ||
|
|
9734f45202 | ||
|
|
a568885ad2 | ||
|
|
b02cb56988 | ||
|
|
3635508a08 | ||
|
|
a44f3e62e3 | ||
|
|
cb9cbdfb28 | ||
|
|
320003bf15 | ||
|
|
b12c5a5ba2 | ||
|
|
ad9bf431a8 | ||
|
|
7737271a30 | ||
|
|
0f08e6699c | ||
|
|
c6775920f5 | ||
|
|
4efe217d9b | ||
|
|
ec120608c2 | ||
|
|
433d640071 | ||
|
|
bc8be9caea | ||
|
|
2872b6cf61 | ||
|
|
9b1b0937eb | ||
|
|
fde82ee323 | ||
|
|
0bec93fa37 | ||
|
|
f9707cc87b | ||
|
|
b71e0302d6 | ||
|
|
2930845b23 | ||
|
|
eb7ad2eb09 | ||
|
|
4021662b48 | ||
|
|
2cfa889750 | ||
|
|
c4589ad4e5 | ||
|
|
16ec9b1e9f | ||
|
|
a691846b5d | ||
|
|
c1a37185b4 | ||
|
|
410b15df92 | ||
|
|
f5975d4039 | ||
|
|
1525901ffc | ||
|
|
3f0393154e | ||
|
|
844a1ebbc6 | ||
|
|
d95c241a1a | ||
|
|
fa03324bbd | ||
|
|
36ded01264 | ||
|
|
07936884a3 | ||
|
|
a64940cf42 | ||
|
|
9e3706e3b9 | ||
|
|
91e6e918c3 | ||
|
|
f0493b22d4 | ||
|
|
02ad93db53 | ||
|
|
e77a06412a | ||
|
|
de1b5626e1 | ||
|
|
39229ce098 | ||
|
|
403496eb92 | ||
|
|
26016b29f7 | ||
|
|
537cc9ed86 | ||
|
|
78222bd51c | ||
|
|
9f6dab0643 | ||
|
|
4cf9beccd8 | ||
|
|
8f9425f09f | ||
|
|
0fa954040e | ||
|
|
e26b8e11d3 | ||
|
|
ced6968e85 | ||
|
|
44e7243e25 | ||
|
|
bbbc9f646f | ||
|
|
cda784c969 | ||
|
|
34ef89b16b | ||
|
|
f8cfaa6147 | ||
|
|
5da5522481 | ||
|
|
cee8004641 | ||
|
|
e1751647f4 | ||
|
|
f33d671a5d | ||
|
|
254b1fd314 | ||
|
|
89c6494056 | ||
|
|
b52cfd3324 | ||
|
|
6329f6bc07 | ||
|
|
57dd62e7d6 | ||
|
|
203820d836 | ||
|
|
e1c486fc4a | ||
|
|
78bbec0a6e | ||
|
|
ffe35c73b6 | ||
|
|
d2385f97a7 | ||
|
|
bd0b8dc0bc | ||
|
|
3f936993a9 | ||
|
|
e5c5790768 | ||
|
|
905bdd0dd5 | ||
|
|
9cbcfca2cd | ||
|
|
e6b8e4fd48 | ||
|
|
8f437c5833 | ||
|
|
d28d909114 | ||
|
|
f67577ebe0 | ||
|
|
70d33129d4 | ||
|
|
a63ce8100e | ||
|
|
d557c6e43e | ||
|
|
fd0404bb4a | ||
|
|
576cf52573 | ||
|
|
e83f0bb7a5 | ||
|
|
fa8e952324 | ||
|
|
25a4679266 | ||
|
|
f5aa4f5866 | ||
|
|
0083649e43 | ||
|
|
2505de35c9 | ||
|
|
238eebb0b6 | ||
|
|
4cb30e69ac | ||
|
|
ac00977e57 | ||
|
|
b2db849798 | ||
|
|
2c7a176580 | ||
|
|
4dbc408696 | ||
|
|
582fd11a70 | ||
|
|
96cb5ff8b0 | ||
|
|
6029e23ab7 | ||
|
|
3434d74993 | ||
|
|
e091793b6c | ||
|
|
9c8444da0e | ||
|
|
427f0f4bee | ||
|
|
95528f875e | ||
|
|
a5f86bff45 | ||
|
|
d991970754 | ||
|
|
d745b44180 | ||
|
|
602fcd6b1b | ||
|
|
b39b0a960e | ||
|
|
40bb796f03 | ||
|
|
2801ba6cad | ||
|
|
5da0ef36ea | ||
|
|
d861292900 | ||
|
|
a3fda43c64 | ||
|
|
8705a26a1a | ||
|
|
2b1c45c28c | ||
|
|
0cf3825183 | ||
|
|
413e1c97d7 | ||
|
|
3b27a3aabf | ||
|
|
4509e13ceb | ||
|
|
33bf8c600b | ||
|
|
b508875f17 | ||
|
|
ac963a2b6e | ||
|
|
13029cf26f | ||
|
|
f39a6b96ff | ||
|
|
c6a17d6832 | ||
|
|
74c0552a12 | ||
|
|
f24b514c9a | ||
|
|
e1c47fdb61 | ||
|
|
93baf24394 |
18
.coveragerc
18
.coveragerc
@@ -91,6 +91,7 @@ omit =
|
||||
homeassistant/components/aurora/__init__.py
|
||||
homeassistant/components/aurora/binary_sensor.py
|
||||
homeassistant/components/aurora/coordinator.py
|
||||
homeassistant/components/aurora/entity.py
|
||||
homeassistant/components/aurora/sensor.py
|
||||
homeassistant/components/avea/light.py
|
||||
homeassistant/components/avion/light.py
|
||||
@@ -123,7 +124,6 @@ omit =
|
||||
homeassistant/components/bluetooth_tracker/*
|
||||
homeassistant/components/bmw_connected_drive/__init__.py
|
||||
homeassistant/components/bmw_connected_drive/binary_sensor.py
|
||||
homeassistant/components/bmw_connected_drive/button.py
|
||||
homeassistant/components/bmw_connected_drive/coordinator.py
|
||||
homeassistant/components/bmw_connected_drive/lock.py
|
||||
homeassistant/components/bmw_connected_drive/notify.py
|
||||
@@ -182,7 +182,6 @@ omit =
|
||||
homeassistant/components/crownstone/listeners.py
|
||||
homeassistant/components/cups/sensor.py
|
||||
homeassistant/components/currencylayer/sensor.py
|
||||
homeassistant/components/daikin/__init__.py
|
||||
homeassistant/components/daikin/climate.py
|
||||
homeassistant/components/daikin/sensor.py
|
||||
homeassistant/components/daikin/switch.py
|
||||
@@ -307,13 +306,9 @@ omit =
|
||||
homeassistant/components/escea/discovery.py
|
||||
homeassistant/components/esphome/__init__.py
|
||||
homeassistant/components/esphome/bluetooth/*
|
||||
homeassistant/components/esphome/button.py
|
||||
homeassistant/components/esphome/camera.py
|
||||
homeassistant/components/esphome/cover.py
|
||||
homeassistant/components/esphome/domain_data.py
|
||||
homeassistant/components/esphome/entry_data.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/esphome/switch.py
|
||||
homeassistant/components/etherscan/sensor.py
|
||||
homeassistant/components/eufy/*
|
||||
homeassistant/components/eufylife_ble/__init__.py
|
||||
@@ -359,10 +354,13 @@ omit =
|
||||
homeassistant/components/fitbit/*
|
||||
homeassistant/components/fivem/__init__.py
|
||||
homeassistant/components/fivem/binary_sensor.py
|
||||
homeassistant/components/fivem/coordinator.py
|
||||
homeassistant/components/fivem/entity.py
|
||||
homeassistant/components/fivem/sensor.py
|
||||
homeassistant/components/fixer/sensor.py
|
||||
homeassistant/components/fjaraskupan/__init__.py
|
||||
homeassistant/components/fjaraskupan/binary_sensor.py
|
||||
homeassistant/components/fjaraskupan/coordinator.py
|
||||
homeassistant/components/fjaraskupan/fan.py
|
||||
homeassistant/components/fjaraskupan/light.py
|
||||
homeassistant/components/fjaraskupan/number.py
|
||||
@@ -942,6 +940,8 @@ omit =
|
||||
homeassistant/components/pyload/sensor.py
|
||||
homeassistant/components/qbittorrent/__init__.py
|
||||
homeassistant/components/qbittorrent/sensor.py
|
||||
homeassistant/components/qnap/__init__.py
|
||||
homeassistant/components/qnap/coordinator.py
|
||||
homeassistant/components/qnap/sensor.py
|
||||
homeassistant/components/qrcode/image_processing.py
|
||||
homeassistant/components/quantum_gateway/device_tracker.py
|
||||
@@ -1046,12 +1046,6 @@ omit =
|
||||
homeassistant/components/sense/__init__.py
|
||||
homeassistant/components/sense/binary_sensor.py
|
||||
homeassistant/components/sense/sensor.py
|
||||
homeassistant/components/senseme/__init__.py
|
||||
homeassistant/components/senseme/discovery.py
|
||||
homeassistant/components/senseme/entity.py
|
||||
homeassistant/components/senseme/fan.py
|
||||
homeassistant/components/senseme/light.py
|
||||
homeassistant/components/senseme/switch.py
|
||||
homeassistant/components/senz/__init__.py
|
||||
homeassistant/components/senz/api.py
|
||||
homeassistant/components/senz/climate.py
|
||||
|
||||
12
.github/workflows/builder.yml
vendored
12
.github/workflows/builder.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_TYPE: core
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
|
||||
jobs:
|
||||
init:
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -274,7 +274,7 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build base image
|
||||
uses: home-assistant/builder@2023.06.0
|
||||
uses: home-assistant/builder@2023.06.1
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -324,12 +324,16 @@ jobs:
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: ["init", "build_base"]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.0.5
|
||||
uses: sigstore/cosign-installer@v3.1.1
|
||||
with:
|
||||
cosign-release: "v2.0.2"
|
||||
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -492,10 +492,10 @@ jobs:
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.0,<23.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -U "pip>=21.3.1,<23.2" setuptools wheel
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_all.txt
|
||||
pip install --cache-dir=$PIP_CACHE -r requirements_test.txt
|
||||
pip install -e .
|
||||
pip install -e . --config-settings editable_mode=compat
|
||||
|
||||
hassfest:
|
||||
name: Check hassfest
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
- "**strings.json"
|
||||
|
||||
env:
|
||||
DEFAULT_PYTHON: "3.10"
|
||||
DEFAULT_PYTHON: "3.11"
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
|
||||
160
.github/workflows/wheels.yml
vendored
160
.github/workflows/wheels.yml
vendored
@@ -47,10 +47,7 @@ jobs:
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
# GRPC on armv7 needs -lexecinfo (issue #56669) since home assistant installs
|
||||
# execinfo-dev when building wheels. The setuptools build setup does not have an option for
|
||||
# adding a single LDFLAG so copy all relevant linux flags here (as of 1.43.0)
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc -lexecinfo"
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc"
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
@@ -83,7 +80,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp310", "cp311"]
|
||||
abi: ["cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@@ -113,7 +110,7 @@ jobs:
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements.txt"
|
||||
|
||||
integrations_cp310:
|
||||
integrations_cp311:
|
||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
@@ -121,7 +118,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp310"]
|
||||
abi: ["cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
@@ -137,152 +134,10 @@ jobs:
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: Uncomment packages
|
||||
run: |
|
||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
||||
for requirement_file in ${requirement_files}; do
|
||||
sed -i "s|# pybluez|pybluez|g" ${requirement_file}
|
||||
sed -i "s|# beacontools|beacontools|g" ${requirement_file}
|
||||
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
||||
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
||||
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
||||
sed -i "s|# pycups|pycups|g" ${requirement_file}
|
||||
sed -i "s|# homekit|homekit|g" ${requirement_file}
|
||||
sed -i "s|# decora_wifi|decora_wifi|g" ${requirement_file}
|
||||
sed -i "s|# python-gammu|python-gammu|g" ${requirement_file}
|
||||
sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
||||
done
|
||||
|
||||
- name: Split requirements all
|
||||
run: |
|
||||
# We split requirements all into two different files.
|
||||
# This is to prevent the build from running out of memory when
|
||||
# resolving packages on 32-bits systems (like armhf, armv7).
|
||||
|
||||
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all.txt requirements_all.txt
|
||||
|
||||
- name: Adjust build env
|
||||
run: |
|
||||
if [ "${{ matrix.arch }}" = "i386" ]; then
|
||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||
fi
|
||||
|
||||
(
|
||||
# cmake > 3.22.2 have issue on arm
|
||||
# Tested until 3.22.5
|
||||
echo "cmake==3.22.2"
|
||||
) >> homeassistant/package_constraints.txt
|
||||
|
||||
# Do not pin numpy in wheels building
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
- name: Build wheels (part 1)
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtaa"
|
||||
|
||||
- name: Build wheels (part 2)
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtab"
|
||||
|
||||
- name: Build wheels (part 3)
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: ${{ matrix.abi }}
|
||||
tag: musllinux_1_2
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
env-file: true
|
||||
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev"
|
||||
skip-binary: aiohttp;grpcio;sqlalchemy;protobuf
|
||||
constraints: "homeassistant/package_constraints.txt"
|
||||
requirements-diff: "requirements_diff.txt"
|
||||
requirements: "requirements_all.txtac"
|
||||
|
||||
# Wheels building for the cp311 ABI is currently split
|
||||
# This is mainly until we have figured out to get all wheels built.
|
||||
# Without harming our current workflow.
|
||||
integrations_cp311:
|
||||
name: Build wheels ${{ matrix.abi }} for ${{ matrix.arch }}
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
needs: init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
abi: ["cp311"]
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.3
|
||||
|
||||
- name: Write alternative env-file for cp311
|
||||
run: |
|
||||
(
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=false"
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=true"
|
||||
echo "GRPC_PYTHON_BUILD_WITH_CYTHON=true"
|
||||
echo "GRPC_PYTHON_DISABLE_LIBC_COMPATIBILITY=true"
|
||||
|
||||
# GRPC on armv7 needed -lexecinfo (issue #56669) since home assistant installed
|
||||
# execinfo-dev when building wheels. However, this package is no longer available
|
||||
# Alpine 3.17, which we use for the cp311 ABI, so the flag should no longer be needed.
|
||||
echo "GRPC_PYTHON_LDFLAGS=-lpthread -Wl,-wrap,memcpy -static-libgcc" # -lexecinfo
|
||||
|
||||
# Fix out of memory issues with rust
|
||||
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
|
||||
|
||||
# OpenCV headless installation
|
||||
echo "CI_BUILD=1"
|
||||
echo "ENABLE_HEADLESS=1"
|
||||
|
||||
# Use C-Extension for sqlalchemy
|
||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
||||
) > .env_file
|
||||
|
||||
- name: Download requirements_diff
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: requirements_diff
|
||||
|
||||
- name: (Un)comment packages
|
||||
run: |
|
||||
requirement_files="requirements_all.txt requirements_diff.txt"
|
||||
for requirement_file in ${requirement_files}; do
|
||||
|
||||
# PyBluez no longer compiles. Commented it out for now.
|
||||
# It need further cleanup down the line, as all machine images
|
||||
# try to install it.
|
||||
# sed -i "s|# pybluez|pybluez|g" ${requirement_file}
|
||||
|
||||
# beacontools requires PyBluez.
|
||||
# sed -i "s|# beacontools|beacontools|g" ${requirement_file}
|
||||
|
||||
# It doesn't build for some reason, so we skip it for now.
|
||||
# Bumping to the latest version (4.7.0.72) supporting Python 3.11
|
||||
# doesn't help. Reverted bump in #91871. There are 8 registered
|
||||
# instances using this integration according to analytics.
|
||||
# sed -i "s|# opencv-python-headless|opencv-python-headless|g" ${requirement_file}
|
||||
|
||||
sed -i "s|# fritzconnection|fritzconnection|g" ${requirement_file}
|
||||
sed -i "s|# pyuserinput|pyuserinput|g" ${requirement_file}
|
||||
sed -i "s|# evdev|evdev|g" ${requirement_file}
|
||||
@@ -319,13 +174,6 @@ jobs:
|
||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||
fi
|
||||
|
||||
# Probably not an issue anymore. Removing for now.
|
||||
# (
|
||||
# # cmake > 3.22.2 have issue on arm
|
||||
# # Tested until 3.22.5
|
||||
# echo "cmake==3.22.2"
|
||||
# ) >> homeassistant/package_constraints.txt
|
||||
|
||||
# Do not pin numpy in wheels building
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
|
||||
|
||||
@@ -277,7 +277,6 @@ homeassistant.components.scene.*
|
||||
homeassistant.components.schedule.*
|
||||
homeassistant.components.scrape.*
|
||||
homeassistant.components.select.*
|
||||
homeassistant.components.senseme.*
|
||||
homeassistant.components.sensibo.*
|
||||
homeassistant.components.sensirion_ble.*
|
||||
homeassistant.components.sensor.*
|
||||
|
||||
@@ -703,6 +703,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/logi_circle/ @evanjd
|
||||
/homeassistant/components/lookin/ @ANMalko @bdraco
|
||||
/tests/components/lookin/ @ANMalko @bdraco
|
||||
/homeassistant/components/loqed/ @mikewoudenberg
|
||||
/tests/components/loqed/ @mikewoudenberg
|
||||
/homeassistant/components/lovelace/ @home-assistant/frontend
|
||||
/tests/components/lovelace/ @home-assistant/frontend
|
||||
/homeassistant/components/luci/ @mzdrale
|
||||
@@ -970,6 +972,7 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/qld_bushfire/ @exxamalte
|
||||
/tests/components/qld_bushfire/ @exxamalte
|
||||
/homeassistant/components/qnap/ @disforw
|
||||
/tests/components/qnap/ @disforw
|
||||
/homeassistant/components/qnap_qsw/ @Noltari
|
||||
/tests/components/qnap_qsw/ @Noltari
|
||||
/homeassistant/components/quantum_gateway/ @cisasteelersfan
|
||||
@@ -1079,8 +1082,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/select/ @home-assistant/core
|
||||
/homeassistant/components/sense/ @kbickar
|
||||
/tests/components/sense/ @kbickar
|
||||
/homeassistant/components/senseme/ @mikelawrence @bdraco
|
||||
/tests/components/senseme/ @mikelawrence @bdraco
|
||||
/homeassistant/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/tests/components/sensibo/ @andrey-git @gjohansson-ST
|
||||
/homeassistant/components/sensirion_ble/ @akx
|
||||
|
||||
@@ -34,6 +34,7 @@ class AbodeAlarm(AbodeDevice, alarm.AlarmControlPanelEntity):
|
||||
"""An alarm_control_panel implementation for Abode."""
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_name = None
|
||||
_attr_code_arm_required = False
|
||||
_attr_supported_features = (
|
||||
AlarmControlPanelEntityFeature.ARM_HOME
|
||||
|
||||
@@ -39,6 +39,7 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
"""Representation of an Abode camera."""
|
||||
|
||||
_device: AbodeCam
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, data: AbodeSystem, device: AbodeDev, event: Event) -> None:
|
||||
"""Initialize the Abode device."""
|
||||
|
||||
@@ -29,6 +29,7 @@ class AbodeCover(AbodeDevice, CoverEntity):
|
||||
"""Representation of an Abode cover."""
|
||||
|
||||
_device: AbodeCV
|
||||
_attr_name = None
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool:
|
||||
|
||||
@@ -42,6 +42,7 @@ class AbodeLight(AbodeDevice, LightEntity):
|
||||
"""Representation of an Abode light."""
|
||||
|
||||
_device: AbodeLT
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the light."""
|
||||
|
||||
@@ -29,6 +29,7 @@ class AbodeLock(AbodeDevice, LockEntity):
|
||||
"""Representation of an Abode lock."""
|
||||
|
||||
_device: AbodeLK
|
||||
_attr_name = None
|
||||
|
||||
def lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the device."""
|
||||
|
||||
@@ -53,7 +53,6 @@ class AbodeSensor(AbodeDevice, SensorEntity):
|
||||
"""A sensor implementation for Abode devices."""
|
||||
|
||||
_device: AbodeSense
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -44,6 +44,7 @@ class AbodeSwitch(AbodeDevice, SwitchEntity):
|
||||
"""Representation of an Abode switch."""
|
||||
|
||||
_device: AbodeSW
|
||||
_attr_name = None
|
||||
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn on the device."""
|
||||
|
||||
@@ -90,6 +90,7 @@ class AdvantageAirAC(AdvantageAirAcEntity, ClimateEntity):
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_max_temp = 32
|
||||
_attr_min_temp = 16
|
||||
_attr_name = None
|
||||
|
||||
_attr_hvac_modes = [
|
||||
HVACMode.OFF,
|
||||
|
||||
@@ -9,17 +9,30 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
|
||||
TO_REDACT = ["dealerPhoneNumber", "latitude", "logoPIN", "longitude", "postCode"]
|
||||
TO_REDACT = [
|
||||
"dealerPhoneNumber",
|
||||
"latitude",
|
||||
"logoPIN",
|
||||
"longitude",
|
||||
"postCode",
|
||||
"rid",
|
||||
"deviceNames",
|
||||
"deviceIds",
|
||||
"deviceIdsV2",
|
||||
"backupId",
|
||||
]
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]["coordinator"].data
|
||||
data = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id].coordinator.data
|
||||
|
||||
# Return only the relevant children
|
||||
return {
|
||||
"aircons": data["aircons"],
|
||||
"aircons": data.get("aircons"),
|
||||
"myLights": data.get("myLights"),
|
||||
"myThings": data.get("myThings"),
|
||||
"system": async_redact_data(data["system"], TO_REDACT),
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ class AdvantageAirZoneEntity(AdvantageAirAcEntity):
|
||||
class AdvantageAirThingEntity(AdvantageAirEntity):
|
||||
"""Parent class for Advantage Air Things Entities."""
|
||||
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, thing: dict[str, Any]) -> None:
|
||||
"""Initialize common aspects of an Advantage Air Things entity."""
|
||||
super().__init__(instance)
|
||||
|
||||
@@ -41,6 +41,7 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
|
||||
"""Representation of Advantage Air Light."""
|
||||
|
||||
_attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, instance: AdvantageAirData, light: dict[str, Any]) -> None:
|
||||
"""Initialize an Advantage Air Light."""
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"city": "City",
|
||||
"country": "Country",
|
||||
"state": "state"
|
||||
"state": "State"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
|
||||
@@ -193,6 +193,8 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set hvac mode."""
|
||||
slave_raise = False
|
||||
|
||||
params = {}
|
||||
if hvac_mode == HVACMode.OFF:
|
||||
params[API_ON] = 0
|
||||
@@ -202,12 +204,13 @@ class AirzoneClimate(AirzoneZoneEntity, ClimateEntity):
|
||||
if self.get_airzone_value(AZD_MASTER):
|
||||
params[API_MODE] = mode
|
||||
else:
|
||||
raise HomeAssistantError(
|
||||
f"Mode can't be changed on slave zone {self.name}"
|
||||
)
|
||||
slave_raise = True
|
||||
params[API_ON] = 1
|
||||
await self._async_update_hvac_params(params)
|
||||
|
||||
if slave_raise:
|
||||
raise HomeAssistantError(f"Mode can't be changed on slave zone {self.name}")
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
params = {}
|
||||
|
||||
@@ -55,7 +55,6 @@ async def async_setup_entry(
|
||||
AirzoneZoneBinarySensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
zone_id,
|
||||
zone_data,
|
||||
)
|
||||
@@ -95,12 +94,11 @@ class AirzoneZoneBinarySensor(AirzoneZoneEntity, AirzoneBinarySensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: AirzoneBinarySensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, zone_id, zone_data)
|
||||
super().__init__(coordinator, zone_id, zone_data)
|
||||
|
||||
self._attr_unique_id = f"{zone_id}_{description.key}"
|
||||
self.entity_description = description
|
||||
|
||||
@@ -15,7 +15,6 @@ from aioairzone_cloud.const import (
|
||||
AZD_ZONES,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -43,7 +42,6 @@ class AirzoneAidooEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
aidoo_id: str,
|
||||
aidoo_data: dict[str, Any],
|
||||
) -> None:
|
||||
@@ -73,7 +71,6 @@ class AirzoneWebServerEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
ws_id: str,
|
||||
ws_data: dict[str, Any],
|
||||
) -> None:
|
||||
@@ -104,7 +101,6 @@ class AirzoneZoneEntity(AirzoneEntity):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioairzone_cloud"],
|
||||
"requirements": ["aioairzone-cloud==0.1.9"]
|
||||
"requirements": ["aioairzone-cloud==0.2.0"]
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ async def async_setup_entry(
|
||||
AirzoneAidooSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
aidoo_id,
|
||||
aidoo_data,
|
||||
)
|
||||
@@ -103,7 +102,6 @@ async def async_setup_entry(
|
||||
AirzoneWebServerSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
ws_id,
|
||||
ws_data,
|
||||
)
|
||||
@@ -117,7 +115,6 @@ async def async_setup_entry(
|
||||
AirzoneZoneSensor(
|
||||
coordinator,
|
||||
description,
|
||||
entry,
|
||||
zone_id,
|
||||
zone_data,
|
||||
)
|
||||
@@ -148,12 +145,11 @@ class AirzoneAidooSensor(AirzoneAidooEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
aidoo_id: str,
|
||||
aidoo_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, aidoo_id, aidoo_data)
|
||||
super().__init__(coordinator, aidoo_id, aidoo_data)
|
||||
|
||||
self._attr_has_entity_name = True
|
||||
self._attr_unique_id = f"{aidoo_id}_{description.key}"
|
||||
@@ -169,12 +165,11 @@ class AirzoneWebServerSensor(AirzoneWebServerEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
ws_id: str,
|
||||
ws_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, ws_id, ws_data)
|
||||
super().__init__(coordinator, ws_id, ws_data)
|
||||
|
||||
self._attr_has_entity_name = True
|
||||
self._attr_unique_id = f"{ws_id}_{description.key}"
|
||||
@@ -190,12 +185,11 @@ class AirzoneZoneSensor(AirzoneZoneEntity, AirzoneSensor):
|
||||
self,
|
||||
coordinator: AirzoneUpdateCoordinator,
|
||||
description: SensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
zone_id: str,
|
||||
zone_data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(coordinator, entry, zone_id, zone_data)
|
||||
super().__init__(coordinator, zone_id, zone_data)
|
||||
|
||||
self._attr_has_entity_name = True
|
||||
self._attr_unique_id = f"{zone_id}_{description.key}"
|
||||
|
||||
@@ -58,7 +58,7 @@ CONDITION_TYPES: Final[set[str]] = {
|
||||
|
||||
CONDITION_SCHEMA: Final = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_TYPE): vol.In(CONDITION_TYPES),
|
||||
}
|
||||
)
|
||||
@@ -83,7 +83,7 @@ async def async_get_conditions(
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
conditions += [
|
||||
@@ -126,8 +126,11 @@ def async_condition_from_config(
|
||||
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
|
||||
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
|
||||
|
||||
registry = er.async_get(hass)
|
||||
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||
|
||||
def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
|
||||
"""Test if an entity is a certain state."""
|
||||
return condition.state(hass, config[ATTR_ENTITY_ID], state)
|
||||
return condition.state(hass, entity_id, state)
|
||||
|
||||
return test_is_state
|
||||
|
||||
@@ -21,12 +21,22 @@ from homeassistant.components.recorder import (
|
||||
DOMAIN as RECORDER_DOMAIN,
|
||||
get_instance as get_recorder_instance,
|
||||
)
|
||||
import homeassistant.config as conf_util
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
)
|
||||
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.helpers.system_info import async_get_system_info
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integrations
|
||||
from homeassistant.loader import (
|
||||
Integration,
|
||||
IntegrationNotFound,
|
||||
async_get_integrations,
|
||||
)
|
||||
from homeassistant.setup import async_get_loaded_integrations
|
||||
|
||||
from .const import (
|
||||
@@ -206,8 +216,25 @@ class Analytics:
|
||||
if self.preferences.get(ATTR_USAGE, False) or self.preferences.get(
|
||||
ATTR_STATISTICS, False
|
||||
):
|
||||
ent_reg = er.async_get(self.hass)
|
||||
|
||||
try:
|
||||
yaml_configuration = await conf_util.async_hass_config_yaml(self.hass)
|
||||
except HomeAssistantError as err:
|
||||
LOGGER.error(err)
|
||||
return
|
||||
|
||||
configuration_set = set(yaml_configuration)
|
||||
er_platforms = {
|
||||
entity.platform
|
||||
for entity in ent_reg.entities.values()
|
||||
if not entity.disabled
|
||||
}
|
||||
|
||||
domains = async_get_loaded_integrations(self.hass)
|
||||
configured_integrations = await async_get_integrations(self.hass, domains)
|
||||
enabled_domains = set(configured_integrations)
|
||||
|
||||
for integration in configured_integrations.values():
|
||||
if isinstance(integration, IntegrationNotFound):
|
||||
continue
|
||||
@@ -215,7 +242,11 @@ class Analytics:
|
||||
if isinstance(integration, BaseException):
|
||||
raise integration
|
||||
|
||||
if integration.disabled:
|
||||
if not self._async_should_report_integration(
|
||||
integration=integration,
|
||||
yaml_domains=configuration_set,
|
||||
entity_registry_platforms=er_platforms,
|
||||
):
|
||||
continue
|
||||
|
||||
if not integration.is_built_in:
|
||||
@@ -253,12 +284,12 @@ class Analytics:
|
||||
if supervisor_info is not None:
|
||||
payload[ATTR_ADDONS] = addons
|
||||
|
||||
if ENERGY_DOMAIN in integrations:
|
||||
if ENERGY_DOMAIN in enabled_domains:
|
||||
payload[ATTR_ENERGY] = {
|
||||
ATTR_CONFIGURED: await energy_is_configured(self.hass)
|
||||
}
|
||||
|
||||
if RECORDER_DOMAIN in integrations:
|
||||
if RECORDER_DOMAIN in enabled_domains:
|
||||
instance = get_recorder_instance(self.hass)
|
||||
engine = instance.database_engine
|
||||
if engine and engine.version is not None:
|
||||
@@ -306,3 +337,34 @@ class Analytics:
|
||||
LOGGER.error(
|
||||
"Error sending analytics to %s: %r", ANALYTICS_ENDPOINT_URL, err
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_should_report_integration(
|
||||
self,
|
||||
integration: Integration,
|
||||
yaml_domains: set[str],
|
||||
entity_registry_platforms: set[str],
|
||||
) -> bool:
|
||||
"""Return a bool to indicate if this integration should be reported."""
|
||||
if integration.disabled:
|
||||
return False
|
||||
|
||||
# Check if the integration is defined in YAML or in the entity registry
|
||||
if (
|
||||
integration.domain in yaml_domains
|
||||
or integration.domain in entity_registry_platforms
|
||||
):
|
||||
return True
|
||||
|
||||
# Check if the integration provide a config flow
|
||||
if not integration.config_flow:
|
||||
return False
|
||||
|
||||
entries = self.hass.config_entries.async_entries(integration.domain)
|
||||
|
||||
# Filter out ignored and disabled entries
|
||||
return any(
|
||||
entry
|
||||
for entry in entries
|
||||
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
|
||||
)
|
||||
|
||||
@@ -296,7 +296,6 @@ class ADBDevice(MediaPlayerEntity):
|
||||
self._process_config,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@property
|
||||
def media_image_hash(self) -> str | None:
|
||||
|
||||
@@ -16,6 +16,7 @@ from .const import DOMAIN
|
||||
class AndroidTVRemoteBaseEntity(Entity):
|
||||
"""Android TV Remote Base Entity."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class AnthemAVR(MediaPlayerEntity):
|
||||
self._attr_name = f"zone {zone_number}"
|
||||
self._attr_unique_id = f"{mac_address}_{zone_number}"
|
||||
else:
|
||||
self._attr_name = None
|
||||
self._attr_unique_id = mac_address
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyatv", "srptools"],
|
||||
"requirements": ["pyatv==0.13.0"],
|
||||
"requirements": ["pyatv==0.13.2"],
|
||||
"zeroconf": [
|
||||
"_mediaremotetv._tcp.local.",
|
||||
"_companion-link._tcp.local.",
|
||||
@@ -16,7 +16,24 @@
|
||||
"_touch-able._tcp.local.",
|
||||
"_appletv-v2._tcp.local.",
|
||||
"_hscp._tcp.local.",
|
||||
"_airplay._tcp.local.",
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"model": "appletv*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"model": "audioaccessory*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "_airplay._tcp.local.",
|
||||
"properties": {
|
||||
"am": "airport*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "_raop._tcp.local.",
|
||||
"properties": {
|
||||
|
||||
@@ -25,6 +25,7 @@ from homeassistant.const import (
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
@@ -81,6 +82,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"interval": AranetSensorEntityDescription(
|
||||
key="update_interval",
|
||||
@@ -90,6 +92,7 @@ SENSOR_DESCRIPTIONS = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
# The interval setting is not a generally useful entity for most users.
|
||||
entity_registry_enabled_default=False,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
||||
from homeassistant.components.device_automation import (
|
||||
DEVICE_TRIGGER_BASE_SCHEMA,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -22,7 +24,7 @@ from .const import DOMAIN, EVENT_TURN_ON
|
||||
TRIGGER_TYPES = {"turn_on"}
|
||||
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES),
|
||||
}
|
||||
)
|
||||
@@ -43,7 +45,7 @@ async def async_get_triggers(
|
||||
CONF_PLATFORM: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
CONF_TYPE: "turn_on",
|
||||
}
|
||||
)
|
||||
@@ -62,7 +64,8 @@ async def async_attach_trigger(
|
||||
job = HassJob(action)
|
||||
|
||||
if config[CONF_TYPE] == "turn_on":
|
||||
entity_id = config[CONF_ENTITY_ID]
|
||||
registry = er.async_get(hass)
|
||||
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||
|
||||
@callback
|
||||
def _handle_event(event: Event) -> None:
|
||||
@@ -74,6 +77,7 @@ async def async_attach_trigger(
|
||||
**trigger_data,
|
||||
**config,
|
||||
"description": f"{DOMAIN} - {entity_id}",
|
||||
"entity_id": entity_id,
|
||||
}
|
||||
},
|
||||
event.context,
|
||||
|
||||
@@ -737,17 +737,30 @@ class PipelineInput:
|
||||
)
|
||||
|
||||
start_stage_index = PIPELINE_STAGE_ORDER.index(self.run.start_stage)
|
||||
end_stage_index = PIPELINE_STAGE_ORDER.index(self.run.end_stage)
|
||||
|
||||
prepare_tasks = []
|
||||
|
||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.STT):
|
||||
if (
|
||||
start_stage_index
|
||||
<= PIPELINE_STAGE_ORDER.index(PipelineStage.STT)
|
||||
<= end_stage_index
|
||||
):
|
||||
# self.stt_metadata can't be None or we'd raise above
|
||||
prepare_tasks.append(self.run.prepare_speech_to_text(self.stt_metadata)) # type: ignore[arg-type]
|
||||
|
||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT):
|
||||
if (
|
||||
start_stage_index
|
||||
<= PIPELINE_STAGE_ORDER.index(PipelineStage.INTENT)
|
||||
<= end_stage_index
|
||||
):
|
||||
prepare_tasks.append(self.run.prepare_recognize_intent())
|
||||
|
||||
if start_stage_index <= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS):
|
||||
if (
|
||||
start_stage_index
|
||||
<= PIPELINE_STAGE_ORDER.index(PipelineStage.TTS)
|
||||
<= end_stage_index
|
||||
):
|
||||
prepare_tasks.append(self.run.prepare_text_to_speech())
|
||||
|
||||
if prepare_tasks:
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
},
|
||||
"vad_sensitivity": {
|
||||
"name": "Silence sensitivity",
|
||||
"name": "Finished speaking detection",
|
||||
"state": {
|
||||
"default": "Default",
|
||||
"aggressive": "Aggressive",
|
||||
|
||||
@@ -137,16 +137,15 @@ class VoiceCommandSegmenter:
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
self._speech_seconds_left = self.speech_seconds
|
||||
elif not is_speech:
|
||||
self._reset_seconds_left = self.reset_seconds
|
||||
self._silence_seconds_left -= self._seconds_per_chunk
|
||||
if self._silence_seconds_left <= 0:
|
||||
return False
|
||||
else:
|
||||
if not is_speech:
|
||||
self._reset_seconds_left = self.reset_seconds
|
||||
self._silence_seconds_left -= self._seconds_per_chunk
|
||||
if self._silence_seconds_left <= 0:
|
||||
return False
|
||||
else:
|
||||
# Reset if enough speech
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
self._silence_seconds_left = self.silence_seconds
|
||||
# Reset if enough speech
|
||||
self._reset_seconds_left -= self._seconds_per_chunk
|
||||
if self._reset_seconds_left <= 0:
|
||||
self._silence_seconds_left = self.silence_seconds
|
||||
|
||||
return True
|
||||
|
||||
@@ -8,14 +8,8 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
AURORA_API,
|
||||
CONF_THRESHOLD,
|
||||
COORDINATOR,
|
||||
@@ -76,34 +70,3 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]):
|
||||
"""Implementation of the base Aurora Entity."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AuroraDataUpdateCoordinator,
|
||||
name: str,
|
||||
icon: str,
|
||||
) -> None:
|
||||
"""Initialize the Aurora Entity."""
|
||||
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = f"{coordinator.latitude}_{coordinator.longitude}"
|
||||
self._attr_icon = icon
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Define the device based on name."""
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, str(self.unique_id))},
|
||||
manufacturer="NOAA",
|
||||
model="Aurora Visibility Sensor",
|
||||
name=self.coordinator.name,
|
||||
)
|
||||
|
||||
@@ -4,8 +4,8 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AuroraEntity
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .entity import AuroraEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
||||
48
homeassistant/components/aurora/entity.py
Normal file
48
homeassistant/components/aurora/entity.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""The aurora component."""
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import AuroraDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuroraEntity(CoordinatorEntity[AuroraDataUpdateCoordinator]):
|
||||
"""Implementation of the base Aurora Entity."""
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AuroraDataUpdateCoordinator,
|
||||
name: str,
|
||||
icon: str,
|
||||
) -> None:
|
||||
"""Initialize the Aurora Entity."""
|
||||
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = f"{coordinator.latitude}_{coordinator.longitude}"
|
||||
self._attr_icon = icon
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Define the device based on name."""
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, str(self.unique_id))},
|
||||
manufacturer="NOAA",
|
||||
model="Aurora Visibility Sensor",
|
||||
name=self.coordinator.name,
|
||||
)
|
||||
@@ -5,8 +5,8 @@ from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AuroraEntity
|
||||
from .const import COORDINATOR, DOMAIN
|
||||
from .entity import AuroraEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
||||
@@ -47,10 +47,10 @@ class AuroraEntity(Entity):
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
||||
"manufacturer": MANUFACTURER,
|
||||
"model": self._data[ATTR_MODEL],
|
||||
"name": self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
||||
"sw_version": self._data[ATTR_FIRMWARE],
|
||||
}
|
||||
return DeviceInfo(
|
||||
identifiers={(DOMAIN, self._data[ATTR_SERIAL_NUMBER])},
|
||||
manufacturer=MANUFACTURER,
|
||||
model=self._data[ATTR_MODEL],
|
||||
name=self._data.get(ATTR_DEVICE_NAME, DEFAULT_DEVICE_NAME),
|
||||
sw_version=self._data[ATTR_FIRMWARE],
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ SENSOR_TYPES = [
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Power Output",
|
||||
translation_key="power_output",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="temp",
|
||||
@@ -42,14 +42,13 @@ SENSOR_TYPES = [
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
name="Temperature",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="totalenergy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
name="Total Energy",
|
||||
translation_key="total_energy",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -75,6 +74,8 @@ async def async_setup_entry(
|
||||
class AuroraSensor(AuroraEntity, SensorEntity):
|
||||
"""Representation of a Sensor on a Aurora ABB PowerOne Solar inverter."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: AuroraSerialClient,
|
||||
|
||||
@@ -18,5 +18,15 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"no_serial_ports": "No com ports found. Need a valid RS485 device to communicate."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"power_output": {
|
||||
"name": "Power Output"
|
||||
},
|
||||
"total_energy": {
|
||||
"name": "Total Energy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Internet Services sensors
|
||||
SensorValueEntityDescription(
|
||||
key="usedMb",
|
||||
name="Data used",
|
||||
translation_key="data_used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -43,7 +43,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="downloadedMb",
|
||||
name="Downloaded",
|
||||
translation_key="downloaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -51,7 +51,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="uploadedMb",
|
||||
name="Uploaded",
|
||||
translation_key="uploaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.MEGABYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -60,21 +60,21 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Mobile Phone Services sensors
|
||||
SensorValueEntityDescription(
|
||||
key="national",
|
||||
name="National calls",
|
||||
translation_key="national_calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="mobile",
|
||||
name="Mobile calls",
|
||||
translation_key="mobile_calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="international",
|
||||
name="International calls",
|
||||
translation_key="international_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone-plus",
|
||||
@@ -82,14 +82,14 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="sms",
|
||||
name="SMS sent",
|
||||
translation_key="sms_sent",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:message-processing",
|
||||
value=lambda x: x.get("calls"),
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="internet",
|
||||
name="Data used",
|
||||
translation_key="data_used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfInformation.KILOBYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
@@ -98,7 +98,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="voicemail",
|
||||
name="Voicemail calls",
|
||||
translation_key="voicemail_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
@@ -106,7 +106,7 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="other",
|
||||
name="Other calls",
|
||||
translation_key="other_calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
@@ -115,13 +115,13 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
|
||||
# Generic sensors
|
||||
SensorValueEntityDescription(
|
||||
key="daysTotal",
|
||||
name="Billing cycle length",
|
||||
translation_key="billing_cycle_length",
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
icon="mdi:calendar-range",
|
||||
),
|
||||
SensorValueEntityDescription(
|
||||
key="daysRemaining",
|
||||
name="Billing cycle remaining",
|
||||
translation_key="billing_cycle_remaining",
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
icon="mdi:calendar-clock",
|
||||
),
|
||||
|
||||
@@ -46,5 +46,42 @@
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"data_used": {
|
||||
"name": "Data used"
|
||||
},
|
||||
"downloaded": {
|
||||
"name": "Downloaded"
|
||||
},
|
||||
"uploaded": {
|
||||
"name": "Uploaded"
|
||||
},
|
||||
"national_calls": {
|
||||
"name": "National calls"
|
||||
},
|
||||
"mobile_calls": {
|
||||
"name": "Mobile calls"
|
||||
},
|
||||
"international_calls": {
|
||||
"name": "International calls"
|
||||
},
|
||||
"sms_sent": {
|
||||
"name": "SMS sent"
|
||||
},
|
||||
"voicemail_calls": {
|
||||
"name": "Voicemail calls"
|
||||
},
|
||||
"other_calls": {
|
||||
"name": "Other calls"
|
||||
},
|
||||
"billing_cycle_length": {
|
||||
"name": "Billing cycle length"
|
||||
},
|
||||
"billing_cycle_remaining": {
|
||||
"name": "Billing cycle remaining"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Allow to set up simple automation rules via the config file."""
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
@@ -153,7 +154,7 @@ def _automations_with_x(
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
return [
|
||||
automation_entity.entity_id
|
||||
@@ -169,7 +170,7 @@ def _x_in_automation(
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||
return []
|
||||
@@ -219,7 +220,7 @@ def automations_with_blueprint(hass: HomeAssistant, blueprint_path: str) -> list
|
||||
if DOMAIN not in hass.data:
|
||||
return []
|
||||
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
return [
|
||||
automation_entity.entity_id
|
||||
@@ -234,7 +235,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||
if DOMAIN not in hass.data:
|
||||
return None
|
||||
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
if (automation_entity := component.get_entity(entity_id)) is None:
|
||||
return None
|
||||
@@ -244,7 +245,7 @@ def blueprint_in_automation(hass: HomeAssistant, entity_id: str) -> str | None:
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up all automations."""
|
||||
hass.data[DOMAIN] = component = EntityComponent[AutomationEntity](
|
||||
hass.data[DOMAIN] = component = EntityComponent[BaseAutomationEntity](
|
||||
LOGGER, DOMAIN, hass
|
||||
)
|
||||
|
||||
@@ -262,7 +263,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
await async_get_blueprints(hass).async_populate()
|
||||
|
||||
async def trigger_service_handler(
|
||||
entity: AutomationEntity, service_call: ServiceCall
|
||||
entity: BaseAutomationEntity, service_call: ServiceCall
|
||||
) -> None:
|
||||
"""Handle forced automation trigger, e.g. from frontend."""
|
||||
await entity.async_trigger(
|
||||
@@ -310,7 +311,103 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
class BaseAutomationEntity(ToggleEntity, ABC):
|
||||
"""Base class for automation entities."""
|
||||
|
||||
raw_config: ConfigType | None
|
||||
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return capability attributes."""
|
||||
if self.unique_id is not None:
|
||||
return {CONF_ID: self.unique_id}
|
||||
return None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_areas(self) -> set[str]:
|
||||
"""Return a set of referenced areas."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
"""Return referenced blueprint or None."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_devices(self) -> set[str]:
|
||||
"""Return a set of referenced devices."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def referenced_entities(self) -> set[str]:
|
||||
"""Return a set of referenced entities."""
|
||||
|
||||
@abstractmethod
|
||||
async def async_trigger(
|
||||
self,
|
||||
run_variables: dict[str, Any],
|
||||
context: Context | None = None,
|
||||
skip_condition: bool = False,
|
||||
) -> None:
|
||||
"""Trigger automation."""
|
||||
|
||||
|
||||
class UnavailableAutomationEntity(BaseAutomationEntity):
|
||||
"""A non-functional automation entity with its state set to unavailable.
|
||||
|
||||
This class is instatiated when an automation fails to validate.
|
||||
"""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_available = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
automation_id: str | None,
|
||||
name: str,
|
||||
raw_config: ConfigType | None,
|
||||
) -> None:
|
||||
"""Initialize an automation entity."""
|
||||
self._name = name
|
||||
self._attr_unique_id = automation_id
|
||||
self.raw_config = raw_config
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the entity."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def referenced_areas(self) -> set[str]:
|
||||
"""Return a set of referenced areas."""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def referenced_blueprint(self) -> str | None:
|
||||
"""Return referenced blueprint or None."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def referenced_devices(self) -> set[str]:
|
||||
"""Return a set of referenced devices."""
|
||||
return set()
|
||||
|
||||
@property
|
||||
def referenced_entities(self) -> set[str]:
|
||||
"""Return a set of referenced entities."""
|
||||
return set()
|
||||
|
||||
async def async_trigger(
|
||||
self,
|
||||
run_variables: dict[str, Any],
|
||||
context: Context | None = None,
|
||||
skip_condition: bool = False,
|
||||
) -> None:
|
||||
"""Trigger automation."""
|
||||
|
||||
|
||||
class AutomationEntity(BaseAutomationEntity, RestoreEntity):
|
||||
"""Entity to show status of entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
@@ -363,8 +460,6 @@ class AutomationEntity(ToggleEntity, RestoreEntity):
|
||||
}
|
||||
if self.action_script.supports_max:
|
||||
attrs[ATTR_MAX] = self.action_script.max_runs
|
||||
if self.unique_id is not None:
|
||||
attrs[CONF_ID] = self.unique_id
|
||||
return attrs
|
||||
|
||||
@property
|
||||
@@ -686,6 +781,7 @@ class AutomationEntityConfig:
|
||||
list_no: int
|
||||
raw_blueprint_inputs: ConfigType | None
|
||||
raw_config: ConfigType | None
|
||||
validation_failed: bool
|
||||
|
||||
|
||||
async def _prepare_automation_config(
|
||||
@@ -700,9 +796,14 @@ async def _prepare_automation_config(
|
||||
for list_no, config_block in enumerate(conf):
|
||||
raw_config = cast(AutomationConfig, config_block).raw_config
|
||||
raw_blueprint_inputs = cast(AutomationConfig, config_block).raw_blueprint_inputs
|
||||
validation_failed = cast(AutomationConfig, config_block).validation_failed
|
||||
automation_configs.append(
|
||||
AutomationEntityConfig(
|
||||
config_block, list_no, raw_blueprint_inputs, raw_config
|
||||
config_block,
|
||||
list_no,
|
||||
raw_blueprint_inputs,
|
||||
raw_config,
|
||||
validation_failed,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -718,9 +819,9 @@ def _automation_name(automation_config: AutomationEntityConfig) -> str:
|
||||
|
||||
async def _create_automation_entities(
|
||||
hass: HomeAssistant, automation_configs: list[AutomationEntityConfig]
|
||||
) -> list[AutomationEntity]:
|
||||
) -> list[BaseAutomationEntity]:
|
||||
"""Create automation entities from prepared configuration."""
|
||||
entities: list[AutomationEntity] = []
|
||||
entities: list[BaseAutomationEntity] = []
|
||||
|
||||
for automation_config in automation_configs:
|
||||
config_block = automation_config.config_block
|
||||
@@ -728,6 +829,16 @@ async def _create_automation_entities(
|
||||
automation_id: str | None = config_block.get(CONF_ID)
|
||||
name = _automation_name(automation_config)
|
||||
|
||||
if automation_config.validation_failed:
|
||||
entities.append(
|
||||
UnavailableAutomationEntity(
|
||||
automation_id,
|
||||
name,
|
||||
automation_config.raw_config,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
initial_state: bool | None = config_block.get(CONF_INITIAL_STATE)
|
||||
|
||||
action_script = Script(
|
||||
@@ -786,18 +897,18 @@ async def _create_automation_entities(
|
||||
async def _async_process_config(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, Any],
|
||||
component: EntityComponent[AutomationEntity],
|
||||
component: EntityComponent[BaseAutomationEntity],
|
||||
) -> None:
|
||||
"""Process config and add automations."""
|
||||
|
||||
def automation_matches_config(
|
||||
automation: AutomationEntity, config: AutomationEntityConfig
|
||||
automation: BaseAutomationEntity, config: AutomationEntityConfig
|
||||
) -> bool:
|
||||
name = _automation_name(config)
|
||||
return automation.name == name and automation.raw_config == config.raw_config
|
||||
|
||||
def find_matches(
|
||||
automations: list[AutomationEntity],
|
||||
automations: list[BaseAutomationEntity],
|
||||
automation_configs: list[AutomationEntityConfig],
|
||||
) -> tuple[set[int], set[int]]:
|
||||
"""Find matches between a list of automation entities and a list of configurations.
|
||||
@@ -843,7 +954,7 @@ async def _async_process_config(
|
||||
return automation_matches, config_matches
|
||||
|
||||
automation_configs = await _prepare_automation_config(hass, config)
|
||||
automations: list[AutomationEntity] = list(component.entities)
|
||||
automations: list[BaseAutomationEntity] = list(component.entities)
|
||||
|
||||
# Find automations and configurations which have matches
|
||||
automation_matches, config_matches = find_matches(automations, automation_configs)
|
||||
@@ -865,8 +976,6 @@ async def _async_process_config(
|
||||
entities = await _create_automation_entities(hass, updated_automation_configs)
|
||||
await component.async_add_entities(entities)
|
||||
|
||||
return
|
||||
|
||||
|
||||
async def _async_process_if(
|
||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||
@@ -970,7 +1079,7 @@ def websocket_config(
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get automation config."""
|
||||
component: EntityComponent[AutomationEntity] = hass.data[DOMAIN]
|
||||
component: EntityComponent[BaseAutomationEntity] = hass.data[DOMAIN]
|
||||
|
||||
automation = component.get_entity(msg["entity_id"])
|
||||
|
||||
|
||||
@@ -41,7 +41,15 @@ from .helpers import async_get_blueprints
|
||||
|
||||
PACKAGE_MERGE_HINT = "list"
|
||||
|
||||
_CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
|
||||
_MINIMAL_PLATFORM_SCHEMA = vol.Schema(
|
||||
{
|
||||
CONF_ID: str,
|
||||
CONF_ALIAS: cv.string,
|
||||
vol.Optional(CONF_DESCRIPTION): cv.string,
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
cv.deprecated(CONF_HIDE_ENTITY),
|
||||
@@ -55,7 +63,7 @@ PLATFORM_SCHEMA = vol.All(
|
||||
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
|
||||
vol.Optional(CONF_HIDE_ENTITY): cv.boolean,
|
||||
vol.Required(CONF_TRIGGER): cv.TRIGGER_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
|
||||
vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA,
|
||||
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||
vol.Optional(CONF_TRIGGER_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
|
||||
@@ -68,6 +76,7 @@ PLATFORM_SCHEMA = vol.All(
|
||||
async def _async_validate_config_item(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
raise_on_errors: bool,
|
||||
warn_on_errors: bool,
|
||||
) -> AutomationConfig:
|
||||
"""Validate config item."""
|
||||
@@ -104,6 +113,15 @@ async def _async_validate_config_item(
|
||||
)
|
||||
return
|
||||
|
||||
def _minimal_config() -> AutomationConfig:
|
||||
"""Try validating id, alias and description."""
|
||||
minimal_config = _MINIMAL_PLATFORM_SCHEMA(config)
|
||||
automation_config = AutomationConfig(minimal_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
|
||||
if blueprint.is_blueprint_instance_config(config):
|
||||
uses_blueprint = True
|
||||
blueprints = async_get_blueprints(hass)
|
||||
@@ -115,7 +133,9 @@ async def _async_validate_config_item(
|
||||
"Failed to generate automation from blueprint: %s",
|
||||
err,
|
||||
)
|
||||
raise
|
||||
if raise_on_errors:
|
||||
raise
|
||||
return _minimal_config()
|
||||
|
||||
raw_blueprint_inputs = blueprint_inputs.config_with_inputs
|
||||
|
||||
@@ -130,7 +150,9 @@ async def _async_validate_config_item(
|
||||
blueprint_inputs.inputs,
|
||||
err,
|
||||
)
|
||||
raise HomeAssistantError from err
|
||||
if raise_on_errors:
|
||||
raise HomeAssistantError(err) from err
|
||||
return _minimal_config()
|
||||
|
||||
automation_name = "Unnamed automation"
|
||||
if isinstance(config, Mapping):
|
||||
@@ -143,10 +165,16 @@ async def _async_validate_config_item(
|
||||
validated_config = PLATFORM_SCHEMA(config)
|
||||
except vol.Invalid as err:
|
||||
_log_invalid_automation(err, automation_name, "could not be validated", config)
|
||||
raise
|
||||
if raise_on_errors:
|
||||
raise
|
||||
return _minimal_config()
|
||||
|
||||
automation_config = AutomationConfig(validated_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
|
||||
try:
|
||||
validated_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||
automation_config[CONF_TRIGGER] = await async_validate_trigger_config(
|
||||
hass, validated_config[CONF_TRIGGER]
|
||||
)
|
||||
except (
|
||||
@@ -156,11 +184,14 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup triggers", validated_config
|
||||
)
|
||||
raise
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
|
||||
if CONF_CONDITION in validated_config:
|
||||
try:
|
||||
validated_config[CONF_CONDITION] = await async_validate_conditions_config(
|
||||
automation_config[CONF_CONDITION] = await async_validate_conditions_config(
|
||||
hass, validated_config[CONF_CONDITION]
|
||||
)
|
||||
except (
|
||||
@@ -170,10 +201,13 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup conditions", validated_config
|
||||
)
|
||||
raise
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
|
||||
try:
|
||||
validated_config[CONF_ACTION] = await script.async_validate_actions_config(
|
||||
automation_config[CONF_ACTION] = await script.async_validate_actions_config(
|
||||
hass, validated_config[CONF_ACTION]
|
||||
)
|
||||
except (
|
||||
@@ -183,11 +217,11 @@ async def _async_validate_config_item(
|
||||
_log_invalid_automation(
|
||||
err, automation_name, "failed to setup actions", validated_config
|
||||
)
|
||||
raise
|
||||
if raise_on_errors:
|
||||
raise
|
||||
automation_config.validation_failed = True
|
||||
return automation_config
|
||||
|
||||
automation_config = AutomationConfig(validated_config)
|
||||
automation_config.raw_blueprint_inputs = raw_blueprint_inputs
|
||||
automation_config.raw_config = raw_config
|
||||
return automation_config
|
||||
|
||||
|
||||
@@ -196,6 +230,7 @@ class AutomationConfig(dict):
|
||||
|
||||
raw_config: dict[str, Any] | None = None
|
||||
raw_blueprint_inputs: dict[str, Any] | None = None
|
||||
validation_failed: bool = False
|
||||
|
||||
|
||||
async def _try_async_validate_config_item(
|
||||
@@ -204,7 +239,7 @@ async def _try_async_validate_config_item(
|
||||
) -> AutomationConfig | None:
|
||||
"""Validate config item."""
|
||||
try:
|
||||
return await _async_validate_config_item(hass, config, True)
|
||||
return await _async_validate_config_item(hass, config, False, True)
|
||||
except (vol.Invalid, HomeAssistantError):
|
||||
return None
|
||||
|
||||
@@ -215,7 +250,7 @@ async def async_validate_config_item(
|
||||
config: dict[str, Any],
|
||||
) -> AutomationConfig | None:
|
||||
"""Validate config item, called by EditAutomationConfigView."""
|
||||
return await _async_validate_config_item(hass, config, False)
|
||||
return await _async_validate_config_item(hass, config, True, False)
|
||||
|
||||
|
||||
async def async_validate_config(hass: HomeAssistant, config: ConfigType) -> ConfigType:
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import gather
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from async_timeout import timeout
|
||||
from python_awair import Awair, AwairLocal
|
||||
from python_awair.air_data import AirData
|
||||
from python_awair.devices import AwairBaseDevice, AwairLocalDevice
|
||||
from python_awair.exceptions import AuthError, AwairError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, Platform
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
CONF_HOST,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -23,7 +29,6 @@ from .const import (
|
||||
LOGGER,
|
||||
UPDATE_INTERVAL_CLOUD,
|
||||
UPDATE_INTERVAL_LOCAL,
|
||||
AwairResult,
|
||||
)
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
@@ -72,6 +77,14 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
return unload_ok
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairResult:
|
||||
"""Wrapper class to hold an awair device and set of air data."""
|
||||
|
||||
device: AwairBaseDevice
|
||||
air_data: AirData
|
||||
|
||||
|
||||
class AwairDataUpdateCoordinator(DataUpdateCoordinator[dict[str, AwairResult]]):
|
||||
"""Define a wrapper class to update Awair data."""
|
||||
|
||||
|
||||
@@ -1,28 +1,9 @@
|
||||
"""Constants for the Awair component."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from python_awair.air_data import AirData
|
||||
from python_awair.devices import AwairBaseDevice
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
|
||||
API_CO2 = "carbon_dioxide"
|
||||
API_DUST = "dust"
|
||||
API_HUMID = "humidity"
|
||||
@@ -39,109 +20,7 @@ ATTRIBUTION = "Awair air quality sensor"
|
||||
|
||||
DOMAIN = "awair"
|
||||
|
||||
DUST_ALIASES = [API_PM25, API_PM10]
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
|
||||
UPDATE_INTERVAL_CLOUD = timedelta(minutes=5)
|
||||
UPDATE_INTERVAL_LOCAL = timedelta(seconds=30)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
unique_id_tag: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairSensorEntityDescription(SensorEntityDescription, AwairRequiredKeysMixin):
|
||||
"""Describes Awair sensor entity."""
|
||||
|
||||
|
||||
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
|
||||
key=API_SCORE,
|
||||
icon="mdi:blur",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
name="Score",
|
||||
unique_id_tag="score", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
)
|
||||
|
||||
SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_HUMID,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
name="Humidity",
|
||||
unique_id_tag="HUMID", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_LUX,
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
name="Illuminance",
|
||||
unique_id_tag="illuminance",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_SPL_A,
|
||||
device_class=SensorDeviceClass.SOUND_PRESSURE,
|
||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||
name="Sound level",
|
||||
unique_id_tag="sound_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_VOC,
|
||||
icon="mdi:molecule",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
name="Volatile organic compounds",
|
||||
unique_id_tag="VOC", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_TEMP,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
name="Temperature",
|
||||
unique_id_tag="TEMP", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_CO2,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
name="Carbon dioxide",
|
||||
unique_id_tag="CO2", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
name="PM2.5",
|
||||
unique_id_tag="PM25", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
name="PM10",
|
||||
unique_id_tag="PM10", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairResult:
|
||||
"""Wrapper class to hold an awair device and set of air data."""
|
||||
|
||||
device: AwairBaseDevice
|
||||
air_data: AirData
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
"""Support for Awair sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, cast
|
||||
|
||||
from python_awair.air_data import AirData
|
||||
from python_awair.devices import AwairBaseDevice, AwairLocalDevice
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_CONNECTIONS, ATTR_SW_VERSION
|
||||
from homeassistant.const import (
|
||||
ATTR_CONNECTIONS,
|
||||
ATTR_SW_VERSION,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
@@ -17,18 +33,106 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import AwairDataUpdateCoordinator, AwairResult
|
||||
from .const import (
|
||||
API_CO2,
|
||||
API_DUST,
|
||||
API_HUMID,
|
||||
API_LUX,
|
||||
API_PM10,
|
||||
API_PM25,
|
||||
API_SCORE,
|
||||
API_SPL_A,
|
||||
API_TEMP,
|
||||
API_VOC,
|
||||
ATTRIBUTION,
|
||||
DOMAIN,
|
||||
DUST_ALIASES,
|
||||
SENSOR_TYPE_SCORE,
|
||||
SENSOR_TYPES,
|
||||
SENSOR_TYPES_DUST,
|
||||
AwairSensorEntityDescription,
|
||||
)
|
||||
|
||||
DUST_ALIASES = [API_PM25, API_PM10]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
unique_id_tag: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class AwairSensorEntityDescription(SensorEntityDescription, AwairRequiredKeysMixin):
|
||||
"""Describes Awair sensor entity."""
|
||||
|
||||
|
||||
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
|
||||
key=API_SCORE,
|
||||
icon="mdi:blur",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
translation_key="score",
|
||||
unique_id_tag="score", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
)
|
||||
|
||||
SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_HUMID,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
unique_id_tag="HUMID", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_LUX,
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
unique_id_tag="illuminance",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_SPL_A,
|
||||
device_class=SensorDeviceClass.SOUND_PRESSURE,
|
||||
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
|
||||
translation_key="sound_level",
|
||||
unique_id_tag="sound_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_VOC,
|
||||
icon="mdi:molecule",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
unique_id_tag="VOC", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_TEMP,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
unique_id_tag="TEMP", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_CO2,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
unique_id_tag="CO2", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM25,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
unique_id_tag="PM25", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
AwairSensorEntityDescription(
|
||||
key=API_PM10,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
unique_id_tag="PM10", # matches legacy format
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -48,5 +48,15 @@
|
||||
"unreachable": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"flow_title": "{model} ({device_id})"
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"score": {
|
||||
"name": "Score"
|
||||
},
|
||||
"sound_level": {
|
||||
"name": "Sound level"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,7 @@ ENTITY_CONDITIONS = {
|
||||
|
||||
CONDITION_SCHEMA = cv.DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_TYPE): vol.In(IS_OFF + IS_ON),
|
||||
vol.Optional(CONF_FOR): cv.positive_time_period_dict,
|
||||
}
|
||||
@@ -287,7 +287,7 @@ async def async_get_conditions(
|
||||
**template,
|
||||
"condition": "device",
|
||||
"device_id": device_id,
|
||||
"entity_id": entry.entity_id,
|
||||
"entity_id": entry.id,
|
||||
"domain": DOMAIN,
|
||||
}
|
||||
for template in templates
|
||||
|
||||
@@ -112,8 +112,8 @@ ENTITY_TRIGGERS = {
|
||||
{CONF_TYPE: CONF_NO_LIGHT},
|
||||
],
|
||||
BinarySensorDeviceClass.LOCK: [
|
||||
{CONF_TYPE: CONF_LOCKED},
|
||||
{CONF_TYPE: CONF_NOT_LOCKED},
|
||||
{CONF_TYPE: CONF_LOCKED},
|
||||
],
|
||||
BinarySensorDeviceClass.MOISTURE: [
|
||||
{CONF_TYPE: CONF_MOIST},
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
}
|
||||
},
|
||||
"device_class": {
|
||||
"co": "carbon_monoxide",
|
||||
"co": "carbon monoxide",
|
||||
"cold": "cold",
|
||||
"gas": "gas",
|
||||
"heat": "heat",
|
||||
|
||||
@@ -41,6 +41,8 @@ class BlinkSyncModule(AlarmControlPanelEntity):
|
||||
|
||||
_attr_icon = ICON
|
||||
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
|
||||
_attr_name = None
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, data, name, sync):
|
||||
"""Initialize the alarm control panel."""
|
||||
@@ -48,9 +50,10 @@ class BlinkSyncModule(AlarmControlPanelEntity):
|
||||
self.sync = sync
|
||||
self._name = name
|
||||
self._attr_unique_id = sync.serial
|
||||
self._attr_name = f"{DOMAIN} {name}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, sync.serial)}, name=name, manufacturer=DEFAULT_BRAND
|
||||
identifiers={(DOMAIN, sync.serial)},
|
||||
name=f"{DOMAIN} {name}",
|
||||
manufacturer=DEFAULT_BRAND,
|
||||
)
|
||||
|
||||
def update(self) -> None:
|
||||
|
||||
@@ -27,17 +27,15 @@ _LOGGER = logging.getLogger(__name__)
|
||||
BINARY_SENSORS_TYPES: tuple[BinarySensorEntityDescription, ...] = (
|
||||
BinarySensorEntityDescription(
|
||||
key=TYPE_BATTERY,
|
||||
name="Battery",
|
||||
device_class=BinarySensorDeviceClass.BATTERY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key=TYPE_CAMERA_ARMED,
|
||||
name="Camera Armed",
|
||||
translation_key="camera_armed",
|
||||
),
|
||||
BinarySensorEntityDescription(
|
||||
key=TYPE_MOTION_DETECTED,
|
||||
name="Motion Detected",
|
||||
device_class=BinarySensorDeviceClass.MOTION,
|
||||
),
|
||||
)
|
||||
@@ -60,13 +58,14 @@ async def async_setup_entry(
|
||||
class BlinkBinarySensor(BinarySensorEntity):
|
||||
"""Representation of a Blink binary sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, data, camera, description: BinarySensorEntityDescription
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.data = data
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{DOMAIN} {camera} {description.name}"
|
||||
self._camera = data.cameras[camera]
|
||||
self._attr_unique_id = f"{self._camera.serial}-{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -38,11 +38,13 @@ async def async_setup_entry(
|
||||
class BlinkCamera(Camera):
|
||||
"""An implementation of a Blink Camera."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, data, name, camera):
|
||||
"""Initialize a camera."""
|
||||
super().__init__()
|
||||
self.data = data
|
||||
self._attr_name = f"{DOMAIN} {name}"
|
||||
self._camera = camera
|
||||
self._attr_unique_id = f"{camera.serial}-camera"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
|
||||
@@ -25,14 +25,13 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_TEMPERATURE,
|
||||
name="Temperature",
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_WIFI_STRENGTH,
|
||||
name="Wifi Signal",
|
||||
translation_key="wifi_rssi",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -57,10 +56,11 @@ async def async_setup_entry(
|
||||
class BlinkSensor(SensorEntity):
|
||||
"""A Blink camera sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, data, camera, description: SensorEntityDescription) -> None:
|
||||
"""Initialize sensors from Blink camera."""
|
||||
self.entity_description = description
|
||||
self._attr_name = f"{DOMAIN} {camera} {description.name}"
|
||||
self.data = data
|
||||
self._camera = data.cameras[camera]
|
||||
self._attr_unique_id = f"{self._camera.serial}-{description.key}"
|
||||
@@ -71,7 +71,7 @@ class BlinkSensor(SensorEntity):
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._camera.serial)},
|
||||
name=camera,
|
||||
name=f"{DOMAIN} {camera}",
|
||||
manufacturer=DEFAULT_BRAND,
|
||||
model=self._camera.camera_type,
|
||||
)
|
||||
|
||||
@@ -34,5 +34,17 @@
|
||||
"description": "Configure Blink integration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"wifi_rssi": {
|
||||
"name": "Wi-Fi RSSI"
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
"camera_armed": {
|
||||
"name": "Camera armed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Import logic for blueprint."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import html
|
||||
import re
|
||||
@@ -28,6 +30,10 @@ GITHUB_FILE_PATTERN = re.compile(
|
||||
r"^https://github.com/(?P<repository>.+)/blob/(?P<path>.+)$"
|
||||
)
|
||||
|
||||
WEBSITE_PATTERN = re.compile(
|
||||
r"^https://(?P<subdomain>[a-z0-9-]+)\.home-assistant\.io/(?P<path>.+).yaml$"
|
||||
)
|
||||
|
||||
COMMUNITY_TOPIC_SCHEMA = vol.Schema(
|
||||
{
|
||||
"slug": str,
|
||||
@@ -219,18 +225,37 @@ async def fetch_blueprint_from_github_gist_url(
|
||||
)
|
||||
|
||||
|
||||
async def fetch_blueprint_from_website_url(
|
||||
hass: HomeAssistant, url: str
|
||||
) -> ImportedBlueprint:
|
||||
"""Get a blueprint from our website."""
|
||||
if (WEBSITE_PATTERN.match(url)) is None:
|
||||
raise UnsupportedUrl("Not a Home Assistant website URL")
|
||||
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
resp = await session.get(url, raise_for_status=True)
|
||||
raw_yaml = await resp.text()
|
||||
data = yaml.parse_yaml(raw_yaml)
|
||||
assert isinstance(data, dict)
|
||||
blueprint = Blueprint(data)
|
||||
|
||||
parsed_import_url = yarl.URL(url)
|
||||
suggested_filename = f"homeassistant/{parsed_import_url.parts[-1][:-5]}"
|
||||
return ImportedBlueprint(suggested_filename, raw_yaml, blueprint)
|
||||
|
||||
|
||||
async def fetch_blueprint_from_url(hass: HomeAssistant, url: str) -> ImportedBlueprint:
|
||||
"""Get a blueprint from a url."""
|
||||
for func in (
|
||||
fetch_blueprint_from_community_post,
|
||||
fetch_blueprint_from_github_url,
|
||||
fetch_blueprint_from_github_gist_url,
|
||||
fetch_blueprint_from_website_url,
|
||||
):
|
||||
try:
|
||||
with suppress(UnsupportedUrl):
|
||||
imported_bp = await func(hass, url)
|
||||
imported_bp.blueprint.update_metadata(source_url=url)
|
||||
return imported_bp
|
||||
except UnsupportedUrl:
|
||||
pass
|
||||
|
||||
raise HomeAssistantError("Unsupported url")
|
||||
raise HomeAssistantError("Unsupported URL")
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"bleak-retry-connector==3.0.2",
|
||||
"bluetooth-adapters==0.15.3",
|
||||
"bluetooth-auto-recovery==1.2.0",
|
||||
"bluetooth-data-tools==1.2.0",
|
||||
"bluetooth-data-tools==1.3.0",
|
||||
"dbus-fast==1.86.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class BMWBinarySensorEntityDescription(
|
||||
SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="lids",
|
||||
name="Lids",
|
||||
translation_key="lids",
|
||||
device_class=BinarySensorDeviceClass.OPENING,
|
||||
icon="mdi:car-door-lock",
|
||||
# device class opening: On means open, Off means closed
|
||||
@@ -134,7 +134,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="windows",
|
||||
name="Windows",
|
||||
translation_key="windows",
|
||||
device_class=BinarySensorDeviceClass.OPENING,
|
||||
icon="mdi:car-door",
|
||||
# device class opening: On means open, Off means closed
|
||||
@@ -145,7 +145,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="door_lock_state",
|
||||
name="Door lock state",
|
||||
translation_key="door_lock_state",
|
||||
device_class=BinarySensorDeviceClass.LOCK,
|
||||
icon="mdi:car-key",
|
||||
# device class lock: On means unlocked, Off means locked
|
||||
@@ -158,7 +158,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="condition_based_services",
|
||||
name="Condition based services",
|
||||
translation_key="condition_based_services",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
icon="mdi:wrench",
|
||||
# device class problem: On means problem detected, Off means no problem
|
||||
@@ -167,7 +167,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="check_control_messages",
|
||||
name="Check control messages",
|
||||
translation_key="check_control_messages",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
icon="mdi:car-tire-alert",
|
||||
# device class problem: On means problem detected, Off means no problem
|
||||
@@ -177,7 +177,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
# electric
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="charging_status",
|
||||
name="Charging status",
|
||||
translation_key="charging_status",
|
||||
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
|
||||
icon="mdi:ev-station",
|
||||
# device class power: On means power detected, Off means no power
|
||||
@@ -185,14 +185,14 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="connection_status",
|
||||
name="Connection status",
|
||||
translation_key="connection_status",
|
||||
device_class=BinarySensorDeviceClass.PLUG,
|
||||
icon="mdi:car-electric",
|
||||
value_fn=lambda v: v.fuel_and_battery.is_charger_connected,
|
||||
),
|
||||
BMWBinarySensorEntityDescription(
|
||||
key="is_pre_entry_climatization_enabled",
|
||||
name="Pre entry climatization",
|
||||
translation_key="is_pre_entry_climatization_enabled",
|
||||
icon="mdi:car-seat-heater",
|
||||
value_fn=lambda v: v.charging_profile.is_pre_entry_climatization_enabled
|
||||
if v.charging_profile
|
||||
|
||||
@@ -6,12 +6,14 @@ from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
from bimmer_connected.vehicle.remote_services import RemoteServiceStatus
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BMWBaseEntity
|
||||
@@ -32,37 +34,45 @@ class BMWButtonEntityDescription(ButtonEntityDescription):
|
||||
[MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus]
|
||||
] | None = None
|
||||
account_function: Callable[[BMWDataUpdateCoordinator], Coroutine] | None = None
|
||||
is_available: Callable[[MyBMWVehicle], bool] = lambda _: True
|
||||
|
||||
|
||||
BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
|
||||
BMWButtonEntityDescription(
|
||||
key="light_flash",
|
||||
translation_key="light_flash",
|
||||
icon="mdi:car-light-alert",
|
||||
name="Flash lights",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_light_flash(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="sound_horn",
|
||||
translation_key="sound_horn",
|
||||
icon="mdi:bullhorn",
|
||||
name="Sound horn",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_horn(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="activate_air_conditioning",
|
||||
translation_key="activate_air_conditioning",
|
||||
icon="mdi:hvac",
|
||||
name="Activate air conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="deactivate_air_conditioning",
|
||||
icon="mdi:hvac-off",
|
||||
name="Deactivate air conditioning",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
|
||||
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="find_vehicle",
|
||||
translation_key="find_vehicle",
|
||||
icon="mdi:crosshairs-question",
|
||||
name="Find vehicle",
|
||||
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_vehicle_finder(),
|
||||
),
|
||||
BMWButtonEntityDescription(
|
||||
key="refresh",
|
||||
translation_key="refresh",
|
||||
icon="mdi:refresh",
|
||||
name="Refresh from cloud",
|
||||
account_function=lambda coordinator: coordinator.async_request_refresh(),
|
||||
enabled_when_read_only=True,
|
||||
),
|
||||
@@ -84,7 +94,7 @@ async def async_setup_entry(
|
||||
[
|
||||
BMWButton(coordinator, vehicle, description)
|
||||
for description in BUTTON_TYPES
|
||||
if not coordinator.read_only
|
||||
if (not coordinator.read_only and description.is_available(vehicle))
|
||||
or (coordinator.read_only and description.enabled_when_read_only)
|
||||
]
|
||||
)
|
||||
@@ -111,7 +121,10 @@ class BMWButton(BMWBaseEntity, ButtonEntity):
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
if self.entity_description.remote_function:
|
||||
await self.entity_description.remote_function(self.vehicle)
|
||||
try:
|
||||
await self.entity_description.remote_function(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
elif self.entity_description.account_function:
|
||||
_LOGGER.warning(
|
||||
"The 'Refresh from cloud' button is deprecated. Use the"
|
||||
@@ -120,6 +133,9 @@ class BMWButton(BMWBaseEntity, ButtonEntity):
|
||||
" https://www.home-assistant.io/integrations/bmw_connected_drive/#update-the-state--refresh-from-api"
|
||||
" for details"
|
||||
)
|
||||
await self.entity_description.account_function(self.coordinator)
|
||||
try:
|
||||
await self.entity_description.account_function(self.coordinator)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
|
||||
self.coordinator.async_update_listeners()
|
||||
|
||||
@@ -21,3 +21,9 @@ UNIT_MAP = {
|
||||
"LITERS": UnitOfVolume.LITERS,
|
||||
"GALLONS": UnitOfVolume.GALLONS,
|
||||
}
|
||||
|
||||
SCAN_INTERVALS = {
|
||||
"china": 300,
|
||||
"north_america": 600,
|
||||
"rest_of_world": 300,
|
||||
}
|
||||
|
||||
@@ -15,10 +15,8 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN
|
||||
from .const import CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN, SCAN_INTERVALS
|
||||
|
||||
DEFAULT_SCAN_INTERVAL_SECONDS = 300
|
||||
SCAN_INTERVAL = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -50,7 +48,7 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"{DOMAIN}-{entry.data['username']}",
|
||||
update_interval=SCAN_INTERVAL,
|
||||
update_interval=timedelta(seconds=SCAN_INTERVALS[entry.data[CONF_REGION]]),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
|
||||
@@ -4,12 +4,14 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
from bimmer_connected.vehicle.doors_windows import LockState
|
||||
|
||||
from homeassistant.components.lock import LockEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BMWBaseEntity
|
||||
@@ -44,7 +46,7 @@ async def async_setup_entry(
|
||||
class BMWLock(BMWBaseEntity, LockEntity):
|
||||
"""Representation of a MyBMW vehicle lock."""
|
||||
|
||||
_attr_name = "Lock"
|
||||
_attr_translation_key = "lock"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -66,7 +68,12 @@ class BMWLock(BMWBaseEntity, LockEntity):
|
||||
# update callback response
|
||||
self._attr_is_locked = True
|
||||
self.async_write_ha_state()
|
||||
await self.vehicle.remote_services.trigger_remote_door_lock()
|
||||
try:
|
||||
await self.vehicle.remote_services.trigger_remote_door_lock()
|
||||
except MyBMWAPIError as ex:
|
||||
self._attr_is_locked = False
|
||||
self.async_write_ha_state()
|
||||
raise HomeAssistantError(ex) from ex
|
||||
|
||||
self.coordinator.async_update_listeners()
|
||||
|
||||
@@ -79,7 +86,12 @@ class BMWLock(BMWBaseEntity, LockEntity):
|
||||
# update callback response
|
||||
self._attr_is_locked = False
|
||||
self.async_write_ha_state()
|
||||
await self.vehicle.remote_services.trigger_remote_door_unlock()
|
||||
try:
|
||||
await self.vehicle.remote_services.trigger_remote_door_unlock()
|
||||
except MyBMWAPIError as ex:
|
||||
self._attr_is_locked = True
|
||||
self.async_write_ha_state()
|
||||
raise HomeAssistantError(ex) from ex
|
||||
|
||||
self.coordinator.async_update_listeners()
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bimmer_connected"],
|
||||
"requirements": ["bimmer-connected==0.13.7"]
|
||||
"requirements": ["bimmer-connected==0.13.8"]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
|
||||
from homeassistant.components.notify import (
|
||||
@@ -19,6 +20,7 @@ from homeassistant.const import (
|
||||
CONF_ENTITY_ID,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import DOMAIN
|
||||
@@ -87,7 +89,11 @@ class BMWNotificationService(BaseNotificationService):
|
||||
if k in ATTR_LOCATION_ATTRIBUTES
|
||||
}
|
||||
)
|
||||
|
||||
await vehicle.remote_services.trigger_send_poi(location_dict)
|
||||
try:
|
||||
await vehicle.remote_services.trigger_send_poi(location_dict)
|
||||
except TypeError as ex:
|
||||
raise ValueError(str(ex)) from ex
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
else:
|
||||
raise ValueError(f"'data.{ATTR_LOCATION}' is required.")
|
||||
|
||||
@@ -45,7 +45,7 @@ class BMWNumberEntityDescription(NumberEntityDescription, BMWRequiredKeysMixin):
|
||||
NUMBER_TYPES: list[BMWNumberEntityDescription] = [
|
||||
BMWNumberEntityDescription(
|
||||
key="target_soc",
|
||||
name="Target SoC",
|
||||
translation_key="target_soc",
|
||||
device_class=NumberDeviceClass.BATTERY,
|
||||
is_available=lambda v: v.is_remote_set_target_soc_enabled,
|
||||
native_max_value=100.0,
|
||||
|
||||
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from bimmer_connected.models import MyBMWAPIError
|
||||
from bimmer_connected.vehicle import MyBMWVehicle
|
||||
from bimmer_connected.vehicle.charging_profile import ChargingMode
|
||||
|
||||
@@ -11,6 +12,7 @@ from homeassistant.components.select import SelectEntity, SelectEntityDescriptio
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfElectricCurrent
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import BMWBaseEntity
|
||||
@@ -39,7 +41,7 @@ class BMWSelectEntityDescription(SelectEntityDescription, BMWRequiredKeysMixin):
|
||||
SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
|
||||
"ac_limit": BMWSelectEntityDescription(
|
||||
key="ac_limit",
|
||||
name="AC Charging Limit",
|
||||
translation_key="ac_limit",
|
||||
is_available=lambda v: v.is_remote_set_ac_limit_enabled,
|
||||
dynamic_options=lambda v: [
|
||||
str(lim) for lim in v.charging_profile.ac_available_limits # type: ignore[union-attr]
|
||||
@@ -53,7 +55,7 @@ SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
|
||||
),
|
||||
"charging_mode": BMWSelectEntityDescription(
|
||||
key="charging_mode",
|
||||
name="Charging Mode",
|
||||
translation_key="charging_mode",
|
||||
is_available=lambda v: v.is_charging_plan_supported,
|
||||
options=[c.value for c in ChargingMode if c != ChargingMode.UNKNOWN],
|
||||
current_option=lambda v: str(v.charging_profile.charging_mode.value), # type: ignore[union-attr]
|
||||
@@ -123,6 +125,9 @@ class BMWSelect(BMWBaseEntity, SelectEntity):
|
||||
self.vehicle.vin,
|
||||
option,
|
||||
)
|
||||
await self.entity_description.remote_service(self.vehicle, option)
|
||||
try:
|
||||
await self.entity_description.remote_service(self.vehicle, option)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(ex) from ex
|
||||
|
||||
self.coordinator.async_update_listeners()
|
||||
|
||||
@@ -55,7 +55,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
# --- Generic ---
|
||||
"ac_current_limit": BMWSensorEntityDescription(
|
||||
key="ac_current_limit",
|
||||
name="AC current limit",
|
||||
translation_key="ac_current_limit",
|
||||
key_class="charging_profile",
|
||||
unit_type=UnitOfElectricCurrent.AMPERE,
|
||||
icon="mdi:current-ac",
|
||||
@@ -63,34 +63,34 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
),
|
||||
"charging_start_time": BMWSensorEntityDescription(
|
||||
key="charging_start_time",
|
||||
name="Charging start time",
|
||||
translation_key="charging_start_time",
|
||||
key_class="fuel_and_battery",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
"charging_end_time": BMWSensorEntityDescription(
|
||||
key="charging_end_time",
|
||||
name="Charging end time",
|
||||
translation_key="charging_end_time",
|
||||
key_class="fuel_and_battery",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
),
|
||||
"charging_status": BMWSensorEntityDescription(
|
||||
key="charging_status",
|
||||
name="Charging status",
|
||||
translation_key="charging_status",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:ev-station",
|
||||
value=lambda x, y: x.value,
|
||||
),
|
||||
"charging_target": BMWSensorEntityDescription(
|
||||
key="charging_target",
|
||||
name="Charging target",
|
||||
translation_key="charging_target",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:battery-charging-high",
|
||||
unit_type=PERCENTAGE,
|
||||
),
|
||||
"remaining_battery_percent": BMWSensorEntityDescription(
|
||||
key="remaining_battery_percent",
|
||||
name="Remaining battery percent",
|
||||
translation_key="remaining_battery_percent",
|
||||
key_class="fuel_and_battery",
|
||||
unit_type=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
@@ -98,14 +98,14 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
# --- Specific ---
|
||||
"mileage": BMWSensorEntityDescription(
|
||||
key="mileage",
|
||||
name="Mileage",
|
||||
translation_key="mileage",
|
||||
icon="mdi:speedometer",
|
||||
unit_type=LENGTH,
|
||||
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
||||
),
|
||||
"remaining_range_total": BMWSensorEntityDescription(
|
||||
key="remaining_range_total",
|
||||
name="Remaining range total",
|
||||
translation_key="remaining_range_total",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:map-marker-distance",
|
||||
unit_type=LENGTH,
|
||||
@@ -113,7 +113,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
),
|
||||
"remaining_range_electric": BMWSensorEntityDescription(
|
||||
key="remaining_range_electric",
|
||||
name="Remaining range electric",
|
||||
translation_key="remaining_range_electric",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:map-marker-distance",
|
||||
unit_type=LENGTH,
|
||||
@@ -121,7 +121,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
),
|
||||
"remaining_range_fuel": BMWSensorEntityDescription(
|
||||
key="remaining_range_fuel",
|
||||
name="Remaining range fuel",
|
||||
translation_key="remaining_range_fuel",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:map-marker-distance",
|
||||
unit_type=LENGTH,
|
||||
@@ -129,7 +129,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
),
|
||||
"remaining_fuel": BMWSensorEntityDescription(
|
||||
key="remaining_fuel",
|
||||
name="Remaining fuel",
|
||||
translation_key="remaining_fuel",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:gas-station",
|
||||
unit_type=VOLUME,
|
||||
@@ -137,7 +137,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
||||
),
|
||||
"remaining_fuel_percent": BMWSensorEntityDescription(
|
||||
key="remaining_fuel_percent",
|
||||
name="Remaining fuel percent",
|
||||
translation_key="remaining_fuel_percent",
|
||||
key_class="fuel_and_battery",
|
||||
icon="mdi:gas-station",
|
||||
unit_type=PERCENTAGE,
|
||||
|
||||
@@ -26,5 +26,114 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"lids": {
|
||||
"name": "Lids"
|
||||
},
|
||||
"windows": {
|
||||
"name": "Windows"
|
||||
},
|
||||
"door_lock_state": {
|
||||
"name": "Door lock state"
|
||||
},
|
||||
"condition_based_services": {
|
||||
"name": "Condition based services"
|
||||
},
|
||||
"check_control_messages": {
|
||||
"name": "Check control messages"
|
||||
},
|
||||
"charging_status": {
|
||||
"name": "Charging status"
|
||||
},
|
||||
"connection_status": {
|
||||
"name": "Connection status"
|
||||
},
|
||||
"is_pre_entry_climatization_enabled": {
|
||||
"name": "Pre entry climatization"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"light_flash": {
|
||||
"name": "Flash lights"
|
||||
},
|
||||
"sound_horn": {
|
||||
"name": "Sound horn"
|
||||
},
|
||||
"activate_air_conditioning": {
|
||||
"name": "Activate air conditioning"
|
||||
},
|
||||
"find_vehicle": {
|
||||
"name": "Find vehicle"
|
||||
},
|
||||
"refresh": {
|
||||
"name": "Refresh from cloud"
|
||||
}
|
||||
},
|
||||
"lock": {
|
||||
"lock": {
|
||||
"name": "[%key:component::lock::title%]"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"target_soc": {
|
||||
"name": "Target SoC"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"ac_limit": {
|
||||
"name": "AC Charging Limit"
|
||||
},
|
||||
"charging_mode": {
|
||||
"name": "Charging Mode"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"ac_current_limit": {
|
||||
"name": "AC current limit"
|
||||
},
|
||||
"charging_start_time": {
|
||||
"name": "Charging start time"
|
||||
},
|
||||
"charging_end_time": {
|
||||
"name": "Charging end time"
|
||||
},
|
||||
"charging_status": {
|
||||
"name": "Charging status"
|
||||
},
|
||||
"charging_target": {
|
||||
"name": "Charging target"
|
||||
},
|
||||
"remaining_battery_percent": {
|
||||
"name": "Remaining battery percent"
|
||||
},
|
||||
"mileage": {
|
||||
"name": "Mileage"
|
||||
},
|
||||
"remaining_range_total": {
|
||||
"name": "Remaining range total"
|
||||
},
|
||||
"remaining_range_electric": {
|
||||
"name": "Remaining range electric"
|
||||
},
|
||||
"remaining_range_fuel": {
|
||||
"name": "Remaining range fuel"
|
||||
},
|
||||
"remaining_fuel": {
|
||||
"name": "Remaining fuel"
|
||||
},
|
||||
"remaining_fuel_percent": {
|
||||
"name": "Remaining fuel percent"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"climate": {
|
||||
"name": "Climate"
|
||||
},
|
||||
"charging": {
|
||||
"name": "Charging"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ CHARGING_STATE_ON = {
|
||||
NUMBER_TYPES: list[BMWSwitchEntityDescription] = [
|
||||
BMWSwitchEntityDescription(
|
||||
key="climate",
|
||||
name="Climate",
|
||||
translation_key="climate",
|
||||
is_available=lambda v: v.is_remote_climate_stop_enabled,
|
||||
value_fn=lambda v: v.climate.is_climate_on,
|
||||
remote_service_on=lambda v: v.remote_services.trigger_remote_air_conditioning(),
|
||||
@@ -60,7 +60,7 @@ NUMBER_TYPES: list[BMWSwitchEntityDescription] = [
|
||||
),
|
||||
BMWSwitchEntityDescription(
|
||||
key="charging",
|
||||
name="Charging",
|
||||
translation_key="charging",
|
||||
is_available=lambda v: v.is_remote_charge_stop_enabled,
|
||||
value_fn=lambda v: v.fuel_and_battery.charging_status in CHARGING_STATE_ON,
|
||||
remote_service_on=lambda v: v.remote_services.trigger_charge_start(),
|
||||
|
||||
@@ -107,6 +107,7 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
|
||||
"""Representation of a Broadlink remote."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device, codes, flags):
|
||||
"""Initialize the remote."""
|
||||
|
||||
@@ -221,6 +221,7 @@ class BroadlinkSP2Switch(BroadlinkSP1Switch):
|
||||
|
||||
_attr_assumed_state = False
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, device, *args, **kwargs):
|
||||
"""Initialize the switch."""
|
||||
|
||||
@@ -34,6 +34,21 @@ class BPKConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
|
||||
"""Import a configuration from config.yaml."""
|
||||
|
||||
if config.get(CONF_LATITUDE):
|
||||
config[CONF_LOCATION] = {
|
||||
CONF_LATITUDE: config[CONF_LATITUDE],
|
||||
CONF_LONGITUDE: config[CONF_LONGITUDE],
|
||||
}
|
||||
if not config.get(CONF_AREA):
|
||||
config[CONF_AREA] = "none"
|
||||
else:
|
||||
config[CONF_AREA] = config[CONF_AREA][0]
|
||||
|
||||
return await self.async_step_user(user_input=config)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
|
||||
@@ -5,19 +5,62 @@ from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
|
||||
from brottsplatskartan import ATTRIBUTION, BrottsplatsKartan
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from .const import CONF_APP_ID, CONF_AREA, DOMAIN, LOGGER
|
||||
from .const import AREAS, CONF_APP_ID, CONF_AREA, DEFAULT_NAME, DOMAIN, LOGGER
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=30)
|
||||
|
||||
PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Inclusive(CONF_LATITUDE, "coordinates"): cv.latitude,
|
||||
vol.Inclusive(CONF_LONGITUDE, "coordinates"): cv.longitude,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_AREA, default=[]): vol.All(cv.ensure_list, [vol.In(AREAS)]),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Brottsplatskartan platform."""
|
||||
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml",
|
||||
breaks_in_ha_version="2023.11.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml",
|
||||
)
|
||||
|
||||
hass.async_create_task(
|
||||
hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_IMPORT},
|
||||
data=config,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
@@ -40,6 +83,7 @@ class BrottsplatskartanSensor(SensorEntity):
|
||||
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
||||
def __init__(self, bpk: BrottsplatsKartan, name: str, entry_id: str) -> None:
|
||||
"""Initialize the Brottsplatskartan sensor."""
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml": {
|
||||
"title": "The Brottsplatskartan YAML configuration is being removed",
|
||||
"description": "Configuring Brottsplatskartan using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the Brottsplatskartan YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"areas": {
|
||||
"options": {
|
||||
|
||||
@@ -71,6 +71,7 @@ class BSBLANClimate(
|
||||
"""Defines a BSBLAN climate device."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
# Determine preset modes
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bthome",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bthome-ble==2.12.0"]
|
||||
"requirements": ["bthome-ble==2.12.1"]
|
||||
}
|
||||
|
||||
@@ -301,55 +301,55 @@
|
||||
"name": "Condition 1d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"condition_2d": {
|
||||
"name": "Condition 2d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"condition_3d": {
|
||||
"name": "Condition 3d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"condition_4d": {
|
||||
"name": "Condition 4d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"condition_5d": {
|
||||
"name": "Condition 5d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::condition::state::clear%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::condition::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::condition::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::condition::state::rainy%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::condition::state::snowy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::condition::state::lightning%]"
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"conditioncode_1d": {
|
||||
@@ -371,76 +371,76 @@
|
||||
"name": "Detailed condition 1d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
||||
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"conditiondetailed_2d": {
|
||||
"name": "Detailed condition 2d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
||||
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"conditiondetailed_3d": {
|
||||
"name": "Detailed condition 3d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
||||
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"conditiondetailed_4d": {
|
||||
"name": "Detailed condition 4d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
||||
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||
@@ -455,21 +455,21 @@
|
||||
"name": "Detailed condition 5d",
|
||||
"state": {
|
||||
"clear": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::clear%]",
|
||||
"partlycloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy%]",
|
||||
"partlycloudy": "[%key:component::weather::entity_component::_::state::partlycloudy%]",
|
||||
"partlycloudy-fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-fog%]",
|
||||
"partlycloudy-light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-rain%]",
|
||||
"partlycloudy-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-rain%]",
|
||||
"cloudy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::cloudy%]",
|
||||
"fog": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::fog%]",
|
||||
"rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::rainy%]",
|
||||
"cloudy": "[%key:component::weather::entity_component::_::state::cloudy%]",
|
||||
"fog": "[%key:component::weather::entity_component::_::state::fog%]",
|
||||
"rainy": "[%key:component::weather::entity_component::_::state::rainy%]",
|
||||
"light-rain": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-rain%]",
|
||||
"light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::light-snow%]",
|
||||
"partlycloudy-light-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-light-snow%]",
|
||||
"partlycloudy-snow": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-snow%]",
|
||||
"partlycloudy-lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::partlycloudy-lightning%]",
|
||||
"snowy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::buienradar::entity::sensor::conditiondetailed::state::lightning%]"
|
||||
"snowy": "[%key:component::weather::entity_component::_::state::snowy%]",
|
||||
"snowy-rainy": "[%key:component::weather::entity_component::_::state::snowy-rainy%]",
|
||||
"lightning": "[%key:component::weather::entity_component::_::state::lightning%]"
|
||||
}
|
||||
},
|
||||
"conditionexact_1d": {
|
||||
|
||||
@@ -92,7 +92,7 @@ class ButtonEntity(RestoreEntity):
|
||||
def _default_to_device_class_name(self) -> bool:
|
||||
"""Return True if an unnamed entity should be named by its device class.
|
||||
|
||||
For sensors this is True if the entity has a device class.
|
||||
For buttons this is True if the entity has a device class.
|
||||
"""
|
||||
return self.device_class is not None
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import async_validate_entity_schema
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -19,14 +20,21 @@ from .const import DOMAIN, SERVICE_PRESS
|
||||
|
||||
ACTION_TYPES = {"press"}
|
||||
|
||||
ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
_ACTION_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): vol.In(ACTION_TYPES),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_validate_action_config(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
return async_validate_entity_schema(hass, config, _ACTION_SCHEMA)
|
||||
|
||||
|
||||
async def async_get_actions(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> list[dict[str, str]]:
|
||||
@@ -36,7 +44,7 @@ async def async_get_actions(
|
||||
{
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
CONF_TYPE: "press",
|
||||
}
|
||||
for entry in er.async_entries_for_device(registry, device_id)
|
||||
|
||||
@@ -60,6 +60,7 @@ from .const import (
|
||||
EVENT_TIME_FIELDS,
|
||||
EVENT_TYPES,
|
||||
EVENT_UID,
|
||||
LIST_EVENT_FIELDS,
|
||||
CalendarEntityFeature,
|
||||
)
|
||||
|
||||
@@ -263,8 +264,8 @@ SERVICE_LIST_EVENTS_SCHEMA: Final = vol.All(
|
||||
cv.has_at_most_one_key(EVENT_END_DATETIME, EVENT_DURATION),
|
||||
cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Optional(EVENT_START_DATETIME): datetime.datetime,
|
||||
vol.Optional(EVENT_END_DATETIME): datetime.datetime,
|
||||
vol.Optional(EVENT_START_DATETIME): cv.datetime,
|
||||
vol.Optional(EVENT_END_DATETIME): cv.datetime,
|
||||
vol.Optional(EVENT_DURATION): vol.All(
|
||||
cv.time_period, cv.positive_timedelta
|
||||
),
|
||||
@@ -415,6 +416,17 @@ def _api_event_dict_factory(obj: Iterable[tuple[str, Any]]) -> dict[str, Any]:
|
||||
return result
|
||||
|
||||
|
||||
def _list_events_dict_factory(
|
||||
obj: Iterable[tuple[str, Any]]
|
||||
) -> dict[str, JsonValueType]:
|
||||
"""Convert CalendarEvent dataclass items to dictionary of attributes."""
|
||||
return {
|
||||
name: value
|
||||
for name, value in _event_dict_factory(obj).items()
|
||||
if name in LIST_EVENT_FIELDS and value is not None
|
||||
}
|
||||
|
||||
|
||||
def _get_datetime_local(
|
||||
dt_or_d: datetime.datetime | datetime.date,
|
||||
) -> datetime.datetime:
|
||||
@@ -781,10 +793,12 @@ async def async_list_events_service(
|
||||
end = start + service_call.data[EVENT_DURATION]
|
||||
else:
|
||||
end = service_call.data[EVENT_END_DATETIME]
|
||||
calendar_event_list = await calendar.async_get_events(calendar.hass, start, end)
|
||||
events: list[JsonValueType] = [
|
||||
dataclasses.asdict(event) for event in calendar_event_list
|
||||
]
|
||||
calendar_event_list = await calendar.async_get_events(
|
||||
calendar.hass, dt_util.as_local(start), dt_util.as_local(end)
|
||||
)
|
||||
return {
|
||||
"events": events,
|
||||
"events": [
|
||||
dataclasses.asdict(event, dict_factory=_list_events_dict_factory)
|
||||
for event in calendar_event_list
|
||||
]
|
||||
}
|
||||
|
||||
@@ -41,3 +41,12 @@ EVENT_TIME_FIELDS = {
|
||||
}
|
||||
EVENT_TYPES = "event_types"
|
||||
EVENT_DURATION = "duration"
|
||||
|
||||
# Fields for the list events service
|
||||
LIST_EVENT_FIELDS = {
|
||||
"start",
|
||||
"end",
|
||||
EVENT_SUMMARY,
|
||||
EVENT_DESCRIPTION,
|
||||
EVENT_LOCATION,
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PORT,
|
||||
EVENT_HOMEASSISTANT_STARTED,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_PORT, DOMAIN
|
||||
@@ -38,19 +38,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
if entry.unique_id is None:
|
||||
hass.config_entries.async_update_entry(entry, unique_id=f"{host}:{port}")
|
||||
|
||||
async def async_finish_startup(_):
|
||||
async def _async_finish_startup(_):
|
||||
await coordinator.async_refresh()
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
if hass.state == CoreState.running:
|
||||
await async_finish_startup(None)
|
||||
else:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STARTED, async_finish_startup
|
||||
)
|
||||
)
|
||||
|
||||
async_at_started(hass, _async_finish_startup)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -534,6 +534,14 @@ class ClimateEntity(Entity):
|
||||
await self.hass.async_add_executor_job(self.turn_on)
|
||||
return
|
||||
|
||||
# If there are only two HVAC modes, and one of those modes is OFF,
|
||||
# then we can just turn on the other mode.
|
||||
if len(self.hvac_modes) == 2 and HVACMode.OFF in self.hvac_modes:
|
||||
for mode in self.hvac_modes:
|
||||
if mode != HVACMode.OFF:
|
||||
await self.async_set_hvac_mode(mode)
|
||||
return
|
||||
|
||||
# Fake turn on
|
||||
for mode in (HVACMode.HEAT_COOL, HVACMode.HEAT, HVACMode.COOL):
|
||||
if mode not in self.hvac_modes:
|
||||
|
||||
@@ -3,6 +3,10 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import (
|
||||
async_get_entity_registry_entry_or_raise,
|
||||
async_validate_entity_schema,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_DEVICE_ID,
|
||||
@@ -24,7 +28,7 @@ ACTION_TYPES = {"set_hvac_mode", "set_preset_mode"}
|
||||
SET_HVAC_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "set_hvac_mode",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(const.ATTR_HVAC_MODE): vol.In(const.HVAC_MODES),
|
||||
}
|
||||
)
|
||||
@@ -32,12 +36,19 @@ SET_HVAC_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
SET_PRESET_MODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_TYPE): "set_preset_mode",
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_domain(DOMAIN),
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(const.ATTR_PRESET_MODE): str,
|
||||
}
|
||||
)
|
||||
|
||||
ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA)
|
||||
_ACTION_SCHEMA = vol.Any(SET_HVAC_MODE_SCHEMA, SET_PRESET_MODE_SCHEMA)
|
||||
|
||||
|
||||
async def async_validate_action_config(
|
||||
hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
return async_validate_entity_schema(hass, config, _ACTION_SCHEMA)
|
||||
|
||||
|
||||
async def async_get_actions(
|
||||
@@ -57,7 +68,7 @@ async def async_get_actions(
|
||||
base_action = {
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
actions.append({**base_action, CONF_TYPE: "set_hvac_mode"})
|
||||
@@ -93,23 +104,24 @@ async def async_get_action_capabilities(
|
||||
) -> dict[str, vol.Schema]:
|
||||
"""List action capabilities."""
|
||||
action_type = config[CONF_TYPE]
|
||||
entity_id_or_uuid = config[CONF_ENTITY_ID]
|
||||
|
||||
fields = {}
|
||||
|
||||
if action_type == "set_hvac_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, entity_id_or_uuid)
|
||||
hvac_modes = (
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_HVAC_MODES)
|
||||
or []
|
||||
get_capability(hass, entry.entity_id, const.ATTR_HVAC_MODES) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
hvac_modes = []
|
||||
fields[vol.Required(const.ATTR_HVAC_MODE)] = vol.In(hvac_modes)
|
||||
elif action_type == "set_preset_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(hass, entity_id_or_uuid)
|
||||
preset_modes = (
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_PRESET_MODES)
|
||||
or []
|
||||
get_capability(hass, entry.entity_id, const.ATTR_PRESET_MODES) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
preset_modes = []
|
||||
|
||||
@@ -3,6 +3,9 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import (
|
||||
async_get_entity_registry_entry_or_raise,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_CONDITION,
|
||||
@@ -28,7 +31,7 @@ CONDITION_TYPES = {"is_hvac_mode", "is_preset_mode"}
|
||||
|
||||
HVAC_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_TYPE): "is_hvac_mode",
|
||||
vol.Required(const.ATTR_HVAC_MODE): vol.In(const.HVAC_MODES),
|
||||
}
|
||||
@@ -36,7 +39,7 @@ HVAC_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||
|
||||
PRESET_MODE_CONDITION = DEVICE_CONDITION_BASE_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
|
||||
vol.Required(CONF_TYPE): "is_preset_mode",
|
||||
vol.Required(const.ATTR_PRESET_MODE): str,
|
||||
}
|
||||
@@ -63,7 +66,7 @@ async def async_get_conditions(
|
||||
CONF_CONDITION: "device",
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
CONF_ENTITY_ID: entry.entity_id,
|
||||
CONF_ENTITY_ID: entry.id,
|
||||
}
|
||||
|
||||
conditions.append({**base_condition, CONF_TYPE: "is_hvac_mode"})
|
||||
@@ -80,9 +83,12 @@ def async_condition_from_config(
|
||||
) -> condition.ConditionCheckerType:
|
||||
"""Create a function to test a device condition."""
|
||||
|
||||
registry = er.async_get(hass)
|
||||
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
|
||||
|
||||
def test_is_state(hass: HomeAssistant, variables: TemplateVarsType) -> bool:
|
||||
"""Test if an entity is a certain state."""
|
||||
if (state := hass.states.get(config[ATTR_ENTITY_ID])) is None:
|
||||
if not entity_id or (state := hass.states.get(entity_id)) is None:
|
||||
return False
|
||||
|
||||
if config[CONF_TYPE] == "is_hvac_mode":
|
||||
@@ -106,9 +112,11 @@ async def async_get_condition_capabilities(
|
||||
|
||||
if condition_type == "is_hvac_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(
|
||||
hass, config[CONF_ENTITY_ID]
|
||||
)
|
||||
hvac_modes = (
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_HVAC_MODES)
|
||||
or []
|
||||
get_capability(hass, entry.entity_id, const.ATTR_HVAC_MODES) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
hvac_modes = []
|
||||
@@ -116,9 +124,11 @@ async def async_get_condition_capabilities(
|
||||
|
||||
elif condition_type == "is_preset_mode":
|
||||
try:
|
||||
entry = async_get_entity_registry_entry_or_raise(
|
||||
hass, config[CONF_ENTITY_ID]
|
||||
)
|
||||
preset_modes = (
|
||||
get_capability(hass, config[ATTR_ENTITY_ID], const.ATTR_PRESET_MODES)
|
||||
or []
|
||||
get_capability(hass, entry.entity_id, const.ATTR_PRESET_MODES) or []
|
||||
)
|
||||
except HomeAssistantError:
|
||||
preset_modes = []
|
||||
|
||||
@@ -142,7 +142,7 @@ async def async_attach_trigger(
|
||||
numeric_state_config[
|
||||
numeric_state_trigger.CONF_VALUE_TEMPLATE
|
||||
] = "{{ state.attributes.current_temperature }}"
|
||||
else:
|
||||
else: # trigger_type == "current_humidity_changed"
|
||||
numeric_state_config[
|
||||
numeric_state_trigger.CONF_VALUE_TEMPLATE
|
||||
] = "{{ state.attributes.current_humidity }}"
|
||||
|
||||
@@ -17,6 +17,7 @@ from homeassistant.components.alexa import (
|
||||
smart_home as alexa_smart_home,
|
||||
)
|
||||
from homeassistant.components.google_assistant import smart_home as ga
|
||||
from homeassistant.const import __version__ as HA_VERSION
|
||||
from homeassistant.core import Context, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
@@ -212,6 +213,19 @@ class CloudClient(Interface):
|
||||
"""Process cloud remote message to client."""
|
||||
await self._prefs.async_update(remote_enabled=connect)
|
||||
|
||||
async def async_cloud_connection_info(
|
||||
self, payload: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Process cloud connection info message to client."""
|
||||
return {
|
||||
"remote": {
|
||||
"connected": self.cloud.remote.is_connected,
|
||||
"enabled": self._prefs.remote_enabled,
|
||||
"instance_domain": self.cloud.remote.instance_domain,
|
||||
},
|
||||
"version": HA_VERSION,
|
||||
}
|
||||
|
||||
async def async_alexa_message(self, payload: dict[Any, Any]) -> dict[Any, Any]:
|
||||
"""Process cloud alexa message to client."""
|
||||
cloud_user = await self._prefs.get_cloud_user()
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["hass_nabucasa"],
|
||||
"requirements": ["hass-nabucasa==0.68.0"]
|
||||
"requirements": ["hass-nabucasa==0.69.0"]
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_binary_sensor",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -73,7 +73,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_cover",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -43,7 +43,7 @@ def get_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_notify",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -74,7 +74,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_sensor",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -74,7 +74,7 @@ async def async_setup_platform(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_switch",
|
||||
breaks_in_ha_version="2023.8.0",
|
||||
breaks_in_ha_version="2023.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_platform_yaml",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""The Compensation integration."""
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
|
||||
import numpy as np
|
||||
import voluptuous as vol
|
||||
@@ -7,6 +8,8 @@ import voluptuous as vol
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_MAXIMUM,
|
||||
CONF_MINIMUM,
|
||||
CONF_SOURCE,
|
||||
CONF_UNIQUE_ID,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
@@ -20,8 +23,10 @@ from .const import (
|
||||
CONF_COMPENSATION,
|
||||
CONF_DATAPOINTS,
|
||||
CONF_DEGREE,
|
||||
CONF_LOWER_LIMIT,
|
||||
CONF_POLYNOMIAL,
|
||||
CONF_PRECISION,
|
||||
CONF_UPPER_LIMIT,
|
||||
DATA_COMPENSATION,
|
||||
DEFAULT_DEGREE,
|
||||
DEFAULT_PRECISION,
|
||||
@@ -50,6 +55,8 @@ COMPENSATION_SCHEMA = vol.Schema(
|
||||
],
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
vol.Optional(CONF_UPPER_LIMIT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_LOWER_LIMIT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): cv.positive_int,
|
||||
vol.Optional(CONF_DEGREE, default=DEFAULT_DEGREE): vol.All(
|
||||
vol.Coerce(int),
|
||||
@@ -78,8 +85,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
degree = conf[CONF_DEGREE]
|
||||
|
||||
initial_coefficients: list[tuple[float, float]] = conf[CONF_DATAPOINTS]
|
||||
sorted_coefficients = sorted(initial_coefficients, key=itemgetter(0))
|
||||
|
||||
# get x values and y values from the x,y point pairs
|
||||
x_values, y_values = zip(*conf[CONF_DATAPOINTS])
|
||||
x_values, y_values = zip(*initial_coefficients)
|
||||
|
||||
# try to get valid coefficients for a polynomial
|
||||
coefficients = None
|
||||
@@ -99,6 +109,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
}
|
||||
data[CONF_POLYNOMIAL] = np.poly1d(coefficients)
|
||||
|
||||
if data[CONF_LOWER_LIMIT]:
|
||||
data[CONF_MINIMUM] = sorted_coefficients[0]
|
||||
else:
|
||||
data[CONF_MINIMUM] = None
|
||||
|
||||
if data[CONF_UPPER_LIMIT]:
|
||||
data[CONF_MAXIMUM] = sorted_coefficients[-1]
|
||||
else:
|
||||
data[CONF_MAXIMUM] = None
|
||||
|
||||
hass.data[DATA_COMPENSATION][compensation] = data
|
||||
|
||||
hass.async_create_task(
|
||||
|
||||
@@ -4,6 +4,8 @@ DOMAIN = "compensation"
|
||||
SENSOR = "compensation"
|
||||
|
||||
CONF_COMPENSATION = "compensation"
|
||||
CONF_LOWER_LIMIT = "lower_limit"
|
||||
CONF_UPPER_LIMIT = "upper_limit"
|
||||
CONF_DATAPOINTS = "data_points"
|
||||
CONF_DEGREE = "degree"
|
||||
CONF_PRECISION = "precision"
|
||||
|
||||
@@ -10,6 +10,8 @@ from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_MAXIMUM,
|
||||
CONF_MINIMUM,
|
||||
CONF_SOURCE,
|
||||
CONF_UNIQUE_ID,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
@@ -64,6 +66,8 @@ async def async_setup_platform(
|
||||
conf[CONF_PRECISION],
|
||||
conf[CONF_POLYNOMIAL],
|
||||
conf.get(CONF_UNIT_OF_MEASUREMENT),
|
||||
conf[CONF_MINIMUM],
|
||||
conf[CONF_MAXIMUM],
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -83,6 +87,8 @@ class CompensationSensor(SensorEntity):
|
||||
precision: int,
|
||||
polynomial: np.poly1d,
|
||||
unit_of_measurement: str | None,
|
||||
minimum: tuple[float, float] | None,
|
||||
maximum: tuple[float, float] | None,
|
||||
) -> None:
|
||||
"""Initialize the Compensation sensor."""
|
||||
self._source_entity_id = source
|
||||
@@ -93,6 +99,8 @@ class CompensationSensor(SensorEntity):
|
||||
self._coefficients = polynomial.coefficients.tolist()
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_name = name
|
||||
self._minimum = minimum
|
||||
self._maximum = maximum
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle added to Hass."""
|
||||
@@ -132,7 +140,14 @@ class CompensationSensor(SensorEntity):
|
||||
else:
|
||||
value = None if new_state.state == STATE_UNKNOWN else new_state.state
|
||||
try:
|
||||
self._attr_native_value = round(self._poly(float(value)), self._precision)
|
||||
x_value = float(value)
|
||||
if self._minimum is not None and x_value <= self._minimum[0]:
|
||||
y_value = self._minimum[1]
|
||||
elif self._maximum is not None and x_value >= self._maximum[0]:
|
||||
y_value = self._maximum[1]
|
||||
else:
|
||||
y_value = self._poly(x_value)
|
||||
self._attr_native_value = round(y_value, self._precision)
|
||||
|
||||
except (ValueError, TypeError):
|
||||
self._attr_native_value = None
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
import re
|
||||
from typing import Any, Literal
|
||||
|
||||
from hassil.recognize import RecognizeResult
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import core
|
||||
@@ -353,6 +354,10 @@ async def websocket_hass_agent_debug(
|
||||
}
|
||||
for entity_key, entity in result.entities.items()
|
||||
},
|
||||
"targets": {
|
||||
state.entity_id: {"matched": is_matched}
|
||||
for state, is_matched in _get_debug_targets(hass, result)
|
||||
},
|
||||
}
|
||||
if result is not None
|
||||
else None
|
||||
@@ -362,6 +367,49 @@ async def websocket_hass_agent_debug(
|
||||
)
|
||||
|
||||
|
||||
def _get_debug_targets(
|
||||
hass: HomeAssistant,
|
||||
result: RecognizeResult,
|
||||
) -> Iterable[tuple[core.State, bool]]:
|
||||
"""Yield state/is_matched pairs for a hassil recognition."""
|
||||
entities = result.entities
|
||||
|
||||
name: str | None = None
|
||||
area_name: str | None = None
|
||||
domains: set[str] | None = None
|
||||
device_classes: set[str] | None = None
|
||||
state_names: set[str] | None = None
|
||||
|
||||
if "name" in entities:
|
||||
name = str(entities["name"].value)
|
||||
|
||||
if "area" in entities:
|
||||
area_name = str(entities["area"].value)
|
||||
|
||||
if "domain" in entities:
|
||||
domains = set(cv.ensure_list(entities["domain"].value))
|
||||
|
||||
if "device_class" in entities:
|
||||
device_classes = set(cv.ensure_list(entities["device_class"].value))
|
||||
|
||||
if "state" in entities:
|
||||
# HassGetState only
|
||||
state_names = set(cv.ensure_list(entities["state"].value))
|
||||
|
||||
states = intent.async_match_states(
|
||||
hass,
|
||||
name=name,
|
||||
area_name=area_name,
|
||||
domains=domains,
|
||||
device_classes=device_classes,
|
||||
)
|
||||
|
||||
for state in states:
|
||||
# For queries, a target is "matched" based on its state
|
||||
is_matched = (state_names is None) or (state.state in state_names)
|
||||
yield state, is_matched
|
||||
|
||||
|
||||
class ConversationProcessView(http.HomeAssistantView):
|
||||
"""View to process text."""
|
||||
|
||||
@@ -529,12 +577,8 @@ class AgentManager:
|
||||
def async_set_agent(self, agent_id: str, agent: AbstractConversationAgent) -> None:
|
||||
"""Set the agent."""
|
||||
self._agents[agent_id] = agent
|
||||
if self.default_agent == HOME_ASSISTANT_AGENT:
|
||||
self.default_agent = agent_id
|
||||
|
||||
@core.callback
|
||||
def async_unset_agent(self, agent_id: str) -> None:
|
||||
"""Unset the agent."""
|
||||
if self.default_agent == agent_id:
|
||||
self.default_agent = HOME_ASSISTANT_AGENT
|
||||
self._agents.pop(agent_id, None)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user