mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-30 15:48:33 +00:00
Compare commits
106
Commits
2.10.1
...
fix/ha-type
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
672d2bf08a | ||
|
|
a80c2db4c6 | ||
|
|
e475de15c9 | ||
|
|
2ca80b8572 | ||
|
|
912fe4c250 | ||
|
|
74478fd955 | ||
|
|
5591207dea | ||
|
|
83ca2746a7 | ||
|
|
8f0d981765 | ||
|
|
6e557162cb | ||
|
|
a2973f21f6 | ||
|
|
f88d99294b | ||
|
|
344776b633 | ||
|
|
4b0c3067ff | ||
|
|
e8ba0b24f8 | ||
|
|
2cfdd807ed | ||
|
|
aa909a8a62 | ||
|
|
f867694831 | ||
|
|
740de2d363 | ||
|
|
e8a7e6f29d | ||
|
|
3760e63183 | ||
|
|
dcb16b8271 | ||
|
|
31fb3a6e89 | ||
|
|
0018ca8f80 | ||
|
|
2f26083ce1 | ||
|
|
8d5fdada38 | ||
|
|
6ab52df08a | ||
|
|
0140708076 | ||
|
|
7775ac5cd2 | ||
|
|
814552a615 | ||
|
|
280442b671 | ||
|
|
628f97f498 | ||
|
|
b58503bb57 | ||
|
|
98a0f94870 | ||
|
|
72be1d746b | ||
|
|
20ab0a2a4f | ||
|
|
87689523ae | ||
|
|
2cbf740f1b | ||
|
|
262139a43f | ||
|
|
e85ff5513c | ||
|
|
9a7cac1e4c | ||
|
|
679436e09b | ||
|
|
c6e7d2dd16 | ||
|
|
29ee3a7552 | ||
|
|
0377f965a3 | ||
|
|
1de8609456 | ||
|
|
95c67c0e22 | ||
|
|
2f44449953 | ||
|
|
299b9e64f5 | ||
|
|
719de7adcf | ||
|
|
758a80b30c | ||
|
|
f4f97d1920 | ||
|
|
a68419f0f5 | ||
|
|
ced6d93d20 | ||
|
|
a81e90b981 | ||
|
|
0cc3b1e7f1 | ||
|
|
fd8bafc81d | ||
|
|
68daf4f1de | ||
|
|
41f6886591 | ||
|
|
582f153896 | ||
|
|
6c2c874641 | ||
|
|
120f6ac779 | ||
|
|
de58711e11 | ||
|
|
ea6ccac938 | ||
|
|
ef8dcb4dc2 | ||
|
|
babf7a17d4 | ||
|
|
f9e1bac588 | ||
|
|
6aac849452 | ||
|
|
2b5a670ba2 | ||
|
|
58feb78362 | ||
|
|
c6e13ccb45 | ||
|
|
ac8c45f67f | ||
|
|
a8364ad67b | ||
|
|
e2b49113c7 | ||
|
|
7b9407d020 | ||
|
|
873ad6082c | ||
|
|
8beaf488f7 | ||
|
|
7a1c2d52f0 | ||
|
|
5a79c338d4 | ||
|
|
16fd422c4a | ||
|
|
e5f70d5ba8 | ||
|
|
0618aca73a | ||
|
|
cfc7ab8dd8 | ||
|
|
56aab4e59c | ||
|
|
7075b78a46 | ||
|
|
b31e8c286e | ||
|
|
2c5b536134 | ||
|
|
52201c96bf | ||
|
|
bf964cc6d9 | ||
|
|
1e114dee70 | ||
|
|
897aa8fb7c | ||
|
|
bbcbed12d5 | ||
|
|
36c4db7f8f | ||
|
|
5458e5084d | ||
|
|
744e4a6d5c | ||
|
|
6ea21c383b | ||
|
|
1a1c09449c | ||
|
|
9426c3ea11 | ||
|
|
4d9e9504ef | ||
|
|
32499d14d5 | ||
|
|
c4fd415cbe | ||
|
|
502030c016 | ||
|
|
7a05464f03 | ||
|
|
15fd9b371e | ||
|
|
04a43652d6 | ||
|
|
7b37e97e00 |
@@ -19,8 +19,12 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
target-branch: dev
|
||||
commit-message:
|
||||
prefix: fix(ignore)
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
target-branch: dev
|
||||
commit-message:
|
||||
prefix: fix(ignore)
|
||||
|
||||
@@ -22,16 +22,16 @@ jobs:
|
||||
actions: read # for Nerivec/action-ci-bench
|
||||
pull-requests: write # for Nerivec/action-ci-bench
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
if: (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push'
|
||||
with:
|
||||
# Required for `release: merge dev -> master and promote dev`
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
if: ((github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) && github.event_name == 'push') == false
|
||||
|
||||
- uses: pnpm/action-setup@v5
|
||||
- uses: pnpm/action-setup@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -216,13 +216,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
node: [20, 22, 24]
|
||||
node: [22, 24, 26]
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: pnpm/action-setup@v5
|
||||
- uses: pnpm/action-setup@v6
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
|
||||
@@ -10,6 +10,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout repository'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: 'Dependency review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
uses: actions/dependency-review-action@v5
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
startsWith(github.event.issue.title, '[New device support]') ||
|
||||
startsWith(github.event.issue.title, '[External Converter]')
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: |
|
||||
scripts
|
||||
path: z2m
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: Koenkk/zigbee-herdsman-converters
|
||||
ref: master
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
merge-master-to-dev:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: devmasx/merge-branch@master
|
||||
with:
|
||||
type: now
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
release_created: ${{ steps.release.outputs.release_created }}
|
||||
version: '${{steps.release.outputs.major}}.${{steps.release.outputs.minor}}.${{steps.release.outputs.patch}}'
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v5
|
||||
- uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 9
|
||||
|
||||
@@ -31,18 +31,18 @@ jobs:
|
||||
token: ${{secrets.GH_TOKEN}}
|
||||
|
||||
# Checkout repos
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: koenkk/zigbee2mqtt
|
||||
path: ./z2m
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
repository: koenkk/zigbee2mqtt
|
||||
path: ./z2m-master
|
||||
ref: master
|
||||
|
||||
- name: Restore cache commit-user-lookup.json
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: z2m/scripts/commit-user-lookup.json
|
||||
key: commit-user-lookup-dummy
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{secrets.GH_TOKEN}}
|
||||
- name: Save cache commit-user-lookup.json
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
if: always()
|
||||
with:
|
||||
path: z2m/scripts/commit-user-lookup.json
|
||||
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: dev
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: pnpm/action-setup@v5
|
||||
- uses: pnpm/action-setup@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "2.10.1"
|
||||
".": "2.12.1"
|
||||
}
|
||||
|
||||
+102
@@ -1,5 +1,107 @@
|
||||
# Changelog
|
||||
|
||||
## [2.12.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.12.0...2.12.1) (2026-06-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add `mqtt.server_name` option to override TLS SNI ([#32417](https://github.com/Koenkk/zigbee2mqtt/issues/32417)) ([2f26083](https://github.com/Koenkk/zigbee2mqtt/commit/2f26083ce13c30227ac8d1d891751d4c253b3572))
|
||||
* Docker: bump alpine from 3.23 to 3.24 ([#32311](https://github.com/Koenkk/zigbee2mqtt/issues/32311)) ([758a80b](https://github.com/Koenkk/zigbee2mqtt/commit/758a80b30c5162b9adb0c2be1785670276905e09))
|
||||
* Home Assistant: add conductivity discovery for soil_fertility ([#32356](https://github.com/Koenkk/zigbee2mqtt/issues/32356)) ([29ee3a7](https://github.com/Koenkk/zigbee2mqtt/commit/29ee3a7552017e648c534708a93329e8b3fc52c5))
|
||||
* Home Assistant: allow discovery user overrides to win over converter ([#32361](https://github.com/Koenkk/zigbee2mqtt/issues/32361)) ([20ab0a2](https://github.com/Koenkk/zigbee2mqtt/commit/20ab0a2a4f44b760b6db73d105dc8578426b27a0))
|
||||
* Home Assistant: apply expose-level Home Assistant discovery metadata ([#32380](https://github.com/Koenkk/zigbee2mqtt/issues/32380)) ([0140708](https://github.com/Koenkk/zigbee2mqtt/commit/0140708076fbd99d33d896e8f214b0e2a8aad8f3))
|
||||
* Home Assistant: mark device settings as config ([#32439](https://github.com/Koenkk/zigbee2mqtt/issues/32439)) ([e8a7e6f](https://github.com/Koenkk/zigbee2mqtt/commit/e8a7e6f29da3773ffc2bda63623298a307fbb597))
|
||||
* Home Assistant: mark legacy action sensors diagnostic ([#32377](https://github.com/Koenkk/zigbee2mqtt/issues/32377)) ([72be1d7](https://github.com/Koenkk/zigbee2mqtt/commit/72be1d746bb79fda65b056e4441dcd73bf6c69c6))
|
||||
* Home Assistant: mark thermostat configuration switches as config ([#32378](https://github.com/Koenkk/zigbee2mqtt/issues/32378)) ([98a0f94](https://github.com/Koenkk/zigbee2mqtt/commit/98a0f9487072cf5a64a901f305c20a73e25c594b))
|
||||
* Home Assistant: pass device options to HA discovery overrides ([#32379](https://github.com/Koenkk/zigbee2mqtt/issues/32379)) ([814552a](https://github.com/Koenkk/zigbee2mqtt/commit/814552a615d33ac24c38160f7a24514c7b358623))
|
||||
* Home Assistant: support cooling setpoint in climate discovery ([#32411](https://github.com/Koenkk/zigbee2mqtt/issues/32411)) ([0018ca8](https://github.com/Koenkk/zigbee2mqtt/commit/0018ca8f809708d22221c9d557b3208ba2e5c057))
|
||||
* Home Assistant: unit conversion for derived weather sensors by restoring device_class with name preservation ([#32392](https://github.com/Koenkk/zigbee2mqtt/issues/32392)) ([7775ac5](https://github.com/Koenkk/zigbee2mqtt/commit/7775ac5cd2ae3929580e401640d97b20facf1491))
|
||||
* **ignore:** bump actions/cache from 5 to 6 ([#32394](https://github.com/Koenkk/zigbee2mqtt/issues/32394)) ([280442b](https://github.com/Koenkk/zigbee2mqtt/commit/280442b6710c03cfa4776d69a14396b29d32c4ba))
|
||||
* **ignore:** bump actions/checkout from 6 to 7 ([#32368](https://github.com/Koenkk/zigbee2mqtt/issues/32368)) ([679436e](https://github.com/Koenkk/zigbee2mqtt/commit/679436e09b40edea5bb17094e9aa9115733b916f))
|
||||
* **ignore:** bump js-yaml from 4.2.0 to 5.0.0 ([#32371](https://github.com/Koenkk/zigbee2mqtt/issues/32371)) ([262139a](https://github.com/Koenkk/zigbee2mqtt/commit/262139a43f878ceef2f327369432e33b74fbd972))
|
||||
* **ignore:** bump semver from 7.8.4 to 7.8.5 in the minor-patch group ([#32369](https://github.com/Koenkk/zigbee2mqtt/issues/32369)) ([9a7cac1](https://github.com/Koenkk/zigbee2mqtt/commit/9a7cac1e4c82c1b0ef566b13620e2d4d438d725e))
|
||||
* **ignore:** bump the minor-patch group with 3 updates ([#32312](https://github.com/Koenkk/zigbee2mqtt/issues/32312)) ([719de7a](https://github.com/Koenkk/zigbee2mqtt/commit/719de7adcfffc64e2e9133142045e5dd56e4e8d4))
|
||||
* **ignore:** update zigbee-herdsman to 10.4.1 ([#32333](https://github.com/Koenkk/zigbee2mqtt/issues/32333)) ([2f44449](https://github.com/Koenkk/zigbee2mqtt/commit/2f4444995361257cbeef0c780532381ec6cc254f))
|
||||
* **ignore:** update zigbee-herdsman to 10.4.2 ([#32388](https://github.com/Koenkk/zigbee2mqtt/issues/32388)) ([628f97f](https://github.com/Koenkk/zigbee2mqtt/commit/628f97f498747cd2e6be5808c7c609b948af528f))
|
||||
* **ignore:** update zigbee-herdsman to 10.5.0 ([#32406](https://github.com/Koenkk/zigbee2mqtt/issues/32406)) ([6ab52df](https://github.com/Koenkk/zigbee2mqtt/commit/6ab52df08af1e9b80eb4b58752b7f403d52844a7))
|
||||
* **ignore:** update zigbee-herdsman to 10.6.0 ([#32422](https://github.com/Koenkk/zigbee2mqtt/issues/32422)) ([dcb16b8](https://github.com/Koenkk/zigbee2mqtt/commit/dcb16b8271349390deee9dc0a0ca4d02239a54bc))
|
||||
* **ignore:** update zigbee-herdsman to 10.6.1 ([#32442](https://github.com/Koenkk/zigbee2mqtt/issues/32442)) ([f867694](https://github.com/Koenkk/zigbee2mqtt/commit/f86769483102762798272655c4fb117f33263489))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.64.0 ([#32282](https://github.com/Koenkk/zigbee2mqtt/issues/32282)) ([a68419f](https://github.com/Koenkk/zigbee2mqtt/commit/a68419f0f58324c1809055938cc59bdd07442aa9))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.65.0 ([#32298](https://github.com/Koenkk/zigbee2mqtt/issues/32298)) ([f4f97d1](https://github.com/Koenkk/zigbee2mqtt/commit/f4f97d19208ab951256b69fd7673bfaba667af65))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.66.0 ([#32315](https://github.com/Koenkk/zigbee2mqtt/issues/32315)) ([299b9e6](https://github.com/Koenkk/zigbee2mqtt/commit/299b9e64f52c99d1d3034321e82fc40a49bec604))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.67.0 ([#32334](https://github.com/Koenkk/zigbee2mqtt/issues/32334)) ([95c67c0](https://github.com/Koenkk/zigbee2mqtt/commit/95c67c0e2271e24a236dabe717772c16f52a3017))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.68.0 ([#32340](https://github.com/Koenkk/zigbee2mqtt/issues/32340)) ([1de8609](https://github.com/Koenkk/zigbee2mqtt/commit/1de860945645e748d6173a0368c4ccb44e9f5f13))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.69.0 ([#32350](https://github.com/Koenkk/zigbee2mqtt/issues/32350)) ([0377f96](https://github.com/Koenkk/zigbee2mqtt/commit/0377f965a3a3c21e42afb885f8b3685d6de2fdd1))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.70.0 ([#32357](https://github.com/Koenkk/zigbee2mqtt/issues/32357)) ([c6e7d2d](https://github.com/Koenkk/zigbee2mqtt/commit/c6e7d2dd16b1a7369b8f784caf3ceed6296a9da2))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.71.0 ([#32375](https://github.com/Koenkk/zigbee2mqtt/issues/32375)) ([8768952](https://github.com/Koenkk/zigbee2mqtt/commit/87689523ae685258fa20ee8c03b7a09e8df0f4ba))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.72.0 ([#32387](https://github.com/Koenkk/zigbee2mqtt/issues/32387)) ([b58503b](https://github.com/Koenkk/zigbee2mqtt/commit/b58503bb57054e24855bcde353b43be6a61273c6))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.73.0 ([#32407](https://github.com/Koenkk/zigbee2mqtt/issues/32407)) ([8d5fdad](https://github.com/Koenkk/zigbee2mqtt/commit/8d5fdada38ce32f8e9ee86b3e39b2e00e2e76529))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.74.0 ([#32420](https://github.com/Koenkk/zigbee2mqtt/issues/32420)) ([31fb3a6](https://github.com/Koenkk/zigbee2mqtt/commit/31fb3a6e895170993e893e9ea0d63c6600859259))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.75.0 ([#32431](https://github.com/Koenkk/zigbee2mqtt/issues/32431)) ([3760e63](https://github.com/Koenkk/zigbee2mqtt/commit/3760e631835c8e6e11cea5241bb2c8cf3d374722))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.76.0 ([#32441](https://github.com/Koenkk/zigbee2mqtt/issues/32441)) ([740de2d](https://github.com/Koenkk/zigbee2mqtt/commit/740de2d363109707a90cdfa8c019361f4423a1aa))
|
||||
* improve zigbee2mqtt maintenance path ([#32255](https://github.com/Koenkk/zigbee2mqtt/issues/32255)) ([ced6d93](https://github.com/Koenkk/zigbee2mqtt/commit/ced6d93d20502f115d4d0b17b5d292fee7ad30e4))
|
||||
* Republish bridge/state online when HA comes online ([#32258](https://github.com/Koenkk/zigbee2mqtt/issues/32258)) ([a81e90b](https://github.com/Koenkk/zigbee2mqtt/commit/a81e90b9812acd795a11a78b6d0489c080dc8f5b))
|
||||
|
||||
## [2.12.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.11.0...2.12.0) (2026-06-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add ability to abort running OTA ([#32022](https://github.com/Koenkk/zigbee2mqtt/issues/32022)) ([de58711](https://github.com/Koenkk/zigbee2mqtt/commit/de58711e11a5ca07c5b55a0c17a1d9f3f452c110))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ignore:** bump the minor-patch group across 1 directory with 3 updates ([#32236](https://github.com/Koenkk/zigbee2mqtt/issues/32236)) ([41f6886](https://github.com/Koenkk/zigbee2mqtt/commit/41f688659119012a9a84063bd67c2769a1b1892c))
|
||||
* **ignore:** bump the minor-patch group with 2 updates ([#32183](https://github.com/Koenkk/zigbee2mqtt/issues/32183)) ([ef8dcb4](https://github.com/Koenkk/zigbee2mqtt/commit/ef8dcb4dc2800defd97fda7523297eab8d2566dc))
|
||||
* **ignore:** missing type for new OTA endpoint ([#32195](https://github.com/Koenkk/zigbee2mqtt/issues/32195)) ([6c2c874](https://github.com/Koenkk/zigbee2mqtt/commit/6c2c874641ada9cae1c9bdd963a3201cb1cf0fef))
|
||||
* **ignore:** update zigbee-herdsman to 10.2.0 ([#32179](https://github.com/Koenkk/zigbee2mqtt/issues/32179)) ([f9e1bac](https://github.com/Koenkk/zigbee2mqtt/commit/f9e1bac588e65b223e75f4a2b2e9d528933fdbd8))
|
||||
* **ignore:** update zigbee-herdsman to 10.3.0 ([#32193](https://github.com/Koenkk/zigbee2mqtt/issues/32193)) ([ea6ccac](https://github.com/Koenkk/zigbee2mqtt/commit/ea6ccac9380af1d9f5672174b2545409f36c2769))
|
||||
* **ignore:** update zigbee-herdsman to 10.4.0 ([#32249](https://github.com/Koenkk/zigbee2mqtt/issues/32249)) ([582f153](https://github.com/Koenkk/zigbee2mqtt/commit/582f1538961af9c1b6434e6a1bc4acebc63a2541))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.61.2 ([#32180](https://github.com/Koenkk/zigbee2mqtt/issues/32180)) ([babf7a1](https://github.com/Koenkk/zigbee2mqtt/commit/babf7a17d4617217297eef45f6b8307265f9b921))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.62.0 ([#32194](https://github.com/Koenkk/zigbee2mqtt/issues/32194)) ([120f6ac](https://github.com/Koenkk/zigbee2mqtt/commit/120f6ac779c9fda6914fc68c56674e589c0e82ba))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.63.0 ([#32252](https://github.com/Koenkk/zigbee2mqtt/issues/32252)) ([68daf4f](https://github.com/Koenkk/zigbee2mqtt/commit/68daf4f1deafad3ee6e542acbd77d403340e2caa))
|
||||
|
||||
## [2.11.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.10.1...2.11.0) (2026-06-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow to disable external JS extensions ([#31826](https://github.com/Koenkk/zigbee2mqtt/issues/31826)) ([15fd9b3](https://github.com/Koenkk/zigbee2mqtt/commit/15fd9b371e30ed352cf2de2d241a0d0f7fafa9d1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps-dev:** bump tmp from 0.2.5 to 0.2.6 ([#32122](https://github.com/Koenkk/zigbee2mqtt/issues/32122)) ([7b9407d](https://github.com/Koenkk/zigbee2mqtt/commit/7b9407d020b9db1093b360f5f6e217bbab7afb63))
|
||||
* **deps:** bump actions/dependency-review-action from 4 to 5 ([#31980](https://github.com/Koenkk/zigbee2mqtt/issues/31980)) ([4d9e950](https://github.com/Koenkk/zigbee2mqtt/commit/4d9e9504ef9c857a1985c79d3a6799552abc3aea))
|
||||
* **deps:** bump brace-expansion from 2.0.2 to 5.0.6 ([#32038](https://github.com/Koenkk/zigbee2mqtt/issues/32038)) ([897aa8f](https://github.com/Koenkk/zigbee2mqtt/commit/897aa8fb7c82dbc9c906f986477c4c1673bb3199))
|
||||
* **deps:** bump pnpm/action-setup from 5 to 6 ([#31698](https://github.com/Koenkk/zigbee2mqtt/issues/31698)) ([1a1c094](https://github.com/Koenkk/zigbee2mqtt/commit/1a1c09449c9d077ea06048d7e2ecea90ff850c9b))
|
||||
* **deps:** bump ws from 8.20.0 to 8.20.1 ([#32041](https://github.com/Koenkk/zigbee2mqtt/issues/32041)) ([bf964cc](https://github.com/Koenkk/zigbee2mqtt/commit/bf964cc6d976d70e687844e5a09c4577763dc0d3))
|
||||
* Fix default value of "optimistic" group setting ([#32054](https://github.com/Koenkk/zigbee2mqtt/issues/32054)) ([b31e8c2](https://github.com/Koenkk/zigbee2mqtt/commit/b31e8c286e7a77517a88e3e7eb2247e9a0fd2cc1))
|
||||
* **ignore:** bump the minor-patch group with 2 updates ([#32137](https://github.com/Koenkk/zigbee2mqtt/issues/32137)) ([ac8c45f](https://github.com/Koenkk/zigbee2mqtt/commit/ac8c45f67f0a4aae2688e55b98dabdc23f73a259))
|
||||
* **ignore:** bump the minor-patch group with 3 updates ([#31982](https://github.com/Koenkk/zigbee2mqtt/issues/31982)) ([9426c3e](https://github.com/Koenkk/zigbee2mqtt/commit/9426c3ea118de9e60326e8dc257a0c2474380f8c))
|
||||
* **ignore:** bump the minor-patch group with 3 updates ([#32106](https://github.com/Koenkk/zigbee2mqtt/issues/32106)) ([e5f70d5](https://github.com/Koenkk/zigbee2mqtt/commit/e5f70d5ba82923f9f37fbda79bed08d581706129))
|
||||
* **ignore:** update zigbee-herdsman to 10.0.8 ([#31945](https://github.com/Koenkk/zigbee2mqtt/issues/31945)) ([04a4365](https://github.com/Koenkk/zigbee2mqtt/commit/04a43652d6e131694e29e06ca646eeac37ed3c7d))
|
||||
* **ignore:** update zigbee-herdsman to 10.1.0 ([#32027](https://github.com/Koenkk/zigbee2mqtt/issues/32027)) ([36c4db7](https://github.com/Koenkk/zigbee2mqtt/commit/36c4db7f8f4e3438f6ebb82cd6ecadf6c54e231e))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.47.0 ([#31946](https://github.com/Koenkk/zigbee2mqtt/issues/31946)) ([7a05464](https://github.com/Koenkk/zigbee2mqtt/commit/7a05464f0355380ee855415bc270632263e3f5d3))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.48.0 ([#31959](https://github.com/Koenkk/zigbee2mqtt/issues/31959)) ([502030c](https://github.com/Koenkk/zigbee2mqtt/commit/502030c0168c8ba6eaf7495a9014489f2f7a7c50))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.49.0 ([#31976](https://github.com/Koenkk/zigbee2mqtt/issues/31976)) ([32499d1](https://github.com/Koenkk/zigbee2mqtt/commit/32499d14d5f0c9eada160d596d6be92da6c4af95))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.50.0 ([#31988](https://github.com/Koenkk/zigbee2mqtt/issues/31988)) ([6ea21c3](https://github.com/Koenkk/zigbee2mqtt/commit/6ea21c383b4801f28e30b022194e71375db94b17))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.51.0 ([#31999](https://github.com/Koenkk/zigbee2mqtt/issues/31999)) ([744e4a6](https://github.com/Koenkk/zigbee2mqtt/commit/744e4a6d5c4dd179e7c3d1a2b4fbcdc820195547))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.51.1 ([#32016](https://github.com/Koenkk/zigbee2mqtt/issues/32016)) ([5458e50](https://github.com/Koenkk/zigbee2mqtt/commit/5458e5084def411aaad9af08e415acb6bc16d181))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.52.0 ([#32040](https://github.com/Koenkk/zigbee2mqtt/issues/32040)) ([1e114de](https://github.com/Koenkk/zigbee2mqtt/commit/1e114dee70b8079eda31d4d0cc52fa073a772798))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.53.0 ([#32051](https://github.com/Koenkk/zigbee2mqtt/issues/32051)) ([52201c9](https://github.com/Koenkk/zigbee2mqtt/commit/52201c96bf897af9bcad30435575923a0452b6dd))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.54.0 ([#32065](https://github.com/Koenkk/zigbee2mqtt/issues/32065)) ([2c5b536](https://github.com/Koenkk/zigbee2mqtt/commit/2c5b53613448ae4c6548f874a36acc1226d53ec3))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.55.0 ([#32068](https://github.com/Koenkk/zigbee2mqtt/issues/32068)) ([7075b78](https://github.com/Koenkk/zigbee2mqtt/commit/7075b78a462a0d1f76cf91cc78601d90edfd8b4e))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.56.0 ([#32087](https://github.com/Koenkk/zigbee2mqtt/issues/32087)) ([56aab4e](https://github.com/Koenkk/zigbee2mqtt/commit/56aab4e59c4ea7bdbd93a1ed6632171994d8ed56))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.57.0 ([#32113](https://github.com/Koenkk/zigbee2mqtt/issues/32113)) ([7a1c2d5](https://github.com/Koenkk/zigbee2mqtt/commit/7a1c2d52f0b0699854f3c8c9375761cb0b22ac55))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.58.0 ([#32126](https://github.com/Koenkk/zigbee2mqtt/issues/32126)) ([e2b4911](https://github.com/Koenkk/zigbee2mqtt/commit/e2b49113c7932c4cd8ef7b23a27e3cd5d4e7150d))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.59.1 ([#32136](https://github.com/Koenkk/zigbee2mqtt/issues/32136)) ([a8364ad](https://github.com/Koenkk/zigbee2mqtt/commit/a8364ad67b0dcbde93b1d6445c269a821321e9b3))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.60.0 ([#32150](https://github.com/Koenkk/zigbee2mqtt/issues/32150)) ([c6e13cc](https://github.com/Koenkk/zigbee2mqtt/commit/c6e13ccb45666f9604e8c9c9655bbe7ea682712d))
|
||||
* **ignore:** update zigbee-herdsman-converters to 26.61.1 ([#32156](https://github.com/Koenkk/zigbee2mqtt/issues/32156)) ([58feb78](https://github.com/Koenkk/zigbee2mqtt/commit/58feb783626fd2429bea0f8a0db37bdc107c431b))
|
||||
* Prevent invalid external JS file name on save ([#32037](https://github.com/Koenkk/zigbee2mqtt/issues/32037)) ([bbcbed1](https://github.com/Koenkk/zigbee2mqtt/commit/bbcbed12d53408eaa8ceec110c7071fff8e158e4))
|
||||
* Use Jinja-safe property access in HA discovery templates ([#31930](https://github.com/Koenkk/zigbee2mqtt/issues/31930)) ([c4fd415](https://github.com/Koenkk/zigbee2mqtt/commit/c4fd415cbe7b01719679b1a96d2b6e07b0793125))
|
||||
|
||||
## [2.10.1](https://github.com/Koenkk/zigbee2mqtt/compare/2.10.0...2.10.1) (2026-05-07)
|
||||
|
||||
|
||||
|
||||
+10
-4
@@ -1,8 +1,14 @@
|
||||
# Contributing to Zigbee2MQTT
|
||||
|
||||
Everybody is invited and welcome to contribute to Zigbee2MQTT. Zigbee2MQTT is written in JavaScript and is based upon [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) and [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters). Zigbee-herdsman-converters contains all device definitions, zigbee-herdsman is responsible for handling all communication with the adapter.
|
||||
> [!WARNING]
|
||||
> Any AI-driven pull request with more than 500 lines of code will not be considered. If wanting to submit something that requires more, split the work into easily review-able pull requests that can be introduced in increments (e.g. pre-refactor, base feature, additional features).
|
||||
|
||||
Everybody is invited and welcomed to contribute to Zigbee2MQTT.
|
||||
Zigbee2MQTT is written in TypeScript.
|
||||
It uses [zigbee-herdsman](https://github.com/koenkk/zigbee-herdsman) for communication with the adapter/coordinator and [zigbee-herdsman-converters](https://github.com/koenkk/zigbee-herdsman-converters) to provide device-specific definitions.
|
||||
|
||||
- Pull requests are always created against the [**dev**](https://github.com/Koenkk/zigbee2mqtt/tree/dev) branch.
|
||||
- Easiest way to start developing Zigbee2MQTT is by setting up a development environment (aka bare-metal installation). You can follow this [guide](https://www.zigbee2mqtt.io/guide/installation/01_linux.html) to do this.
|
||||
- You can run the tests locally by executing `pnpm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if your code is covered by running `pnpm run test:coverage`. The coverage report can be found under `coverage/lcov-report/index.html`. Linting is also enforced and can be run with `pnpm run eslint`.
|
||||
- When you want to add support for a new device no changes to Zigbee2MQTT have to be made, only to zigbee-herdsman-converters. You can find a guide for it [here](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html).
|
||||
- Easiest way to start developing Zigbee2MQTT is by setting up a development environment (a.k.a. bare-metal installation). You can follow this [guide](https://www.zigbee2mqtt.io/guide/installation/01_linux.html) to do this.
|
||||
- You can run the tests locally by executing `pnpm test`. Zigbee2MQTT enforces 100% code coverage, in case you add new code check if your code is covered by running `pnpm run test:coverage`. The coverage report can be found under `coverage/lcov-report/index.html`.
|
||||
- Linting & formatting is also enforced and can be run with `pnpm run check` (can use `pnpm run check:w` to fix small issues automatically).
|
||||
- If you want to add support for a new device no change to Zigbee2MQTT has to be made, only to zigbee-herdsman-converters. You can find a guide for it [here](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html).
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
@@ -12,7 +12,7 @@
|
||||
"bracketSpacing": false
|
||||
},
|
||||
"files": {
|
||||
"includes": ["**", "!package.json", "!!**/dist", "!!**/coverage"]
|
||||
"includes": ["**", "!package.json", "!!**/dist", "!!**/coverage", "!images"]
|
||||
},
|
||||
"linter": {
|
||||
"includes": ["**"],
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
FROM alpine:3.23 AS base
|
||||
FROM alpine:3.24 AS base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
|
||||
@@ -12,7 +12,7 @@ let unsolicitedStop = false;
|
||||
let watchdogDelays = [2000, 60000, 300000, 900000, 1800000, 3600000];
|
||||
|
||||
if (process.env.Z2M_WATCHDOG != null && process.env.Z2M_WATCHDOG !== "default") {
|
||||
if (/^\d+(.\d+)?(,\d+(.\d+)?)*$/.test(process.env.Z2M_WATCHDOG)) {
|
||||
if (/^\d+(\.\d+)?(,\d+(\.\d+)?)*$/.test(process.env.Z2M_WATCHDOG)) {
|
||||
watchdogDelays = process.env.Z2M_WATCHDOG.split(",").map((v) => Number.parseFloat(v) * 60000);
|
||||
} else {
|
||||
console.log(`Invalid watchdog delays (must use number-only CSV format representing minutes, example: 'Z2M_WATCHDOG=1,5,15,30,60'.`);
|
||||
|
||||
+25
-17
@@ -50,7 +50,6 @@ export class Controller {
|
||||
this.state = new State(this.eventBus, this.zigbee);
|
||||
this.restartCallback = restartCallback;
|
||||
this.exitCallback = exitCallback;
|
||||
|
||||
// Initialize extensions.
|
||||
this.extensionArgs = [
|
||||
this.zigbee,
|
||||
@@ -62,22 +61,29 @@ export class Controller {
|
||||
this.restartCallback,
|
||||
this.addExtension,
|
||||
];
|
||||
this.extensions = new Set();
|
||||
|
||||
this.extensions = new Set([
|
||||
new ExtensionExternalConverters(...this.extensionArgs),
|
||||
new ExtensionOnEvent(...this.extensionArgs),
|
||||
new ExtensionBridge(...this.extensionArgs),
|
||||
new ExtensionPublish(...this.extensionArgs),
|
||||
new ExtensionReceive(...this.extensionArgs),
|
||||
new ExtensionConfigure(...this.extensionArgs),
|
||||
new ExtensionNetworkMap(...this.extensionArgs),
|
||||
new ExtensionGroups(...this.extensionArgs),
|
||||
new ExtensionBind(...this.extensionArgs),
|
||||
new ExtensionOTAUpdate(...this.extensionArgs),
|
||||
new ExtensionExternalExtensions(...this.extensionArgs),
|
||||
new ExtensionAvailability(...this.extensionArgs),
|
||||
new ExtensionHealth(...this.extensionArgs),
|
||||
]);
|
||||
if (settings.get().advanced.enable_external_js) {
|
||||
this.extensions.add(new ExtensionExternalConverters(...this.extensionArgs));
|
||||
} else {
|
||||
logger.info("External JS (converters/extensions) is disabled");
|
||||
}
|
||||
|
||||
this.extensions.add(new ExtensionOnEvent(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionBridge(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionPublish(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionReceive(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionConfigure(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionNetworkMap(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionGroups(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionBind(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionOTAUpdate(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionAvailability(...this.extensionArgs));
|
||||
this.extensions.add(new ExtensionHealth(...this.extensionArgs));
|
||||
|
||||
if (settings.get().advanced.enable_external_js) {
|
||||
this.extensions.add(new ExtensionExternalExtensions(...this.extensionArgs));
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
@@ -247,10 +253,12 @@ export class Controller {
|
||||
}
|
||||
|
||||
const existingExtension = this.getExtension(name);
|
||||
|
||||
if (existingExtension) {
|
||||
await this.removeExtension(existingExtension);
|
||||
}
|
||||
await this.extensions.add(extension);
|
||||
|
||||
this.extensions.add(extension);
|
||||
} else {
|
||||
switch (name) {
|
||||
case "Frontend": {
|
||||
|
||||
+6
-5
@@ -247,19 +247,20 @@ export default class EventBus {
|
||||
this.callbacksByExtension.set(key.constructor.name, []);
|
||||
}
|
||||
|
||||
const wrappedCallback = async (...args: never[]): Promise<void> => {
|
||||
const typedCallback = callback as (...args: EventBusMap[K]) => Promise<void> | void;
|
||||
const wrappedCallback = (async (...args: EventBusMap[K]): Promise<void> => {
|
||||
try {
|
||||
await callback(...args);
|
||||
await typedCallback(...args);
|
||||
} catch (error) {
|
||||
logger.error(`EventBus error '${key.constructor.name}/${event}': ${(error as Error).message}`);
|
||||
// biome-ignore lint/style/noNonNullAssertion: always Error
|
||||
logger.debug((error as Error).stack!);
|
||||
}
|
||||
};
|
||||
}) as EventBusListener<keyof EventBusMap>;
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: just created if wasn't valid
|
||||
this.callbacksByExtension.get(key.constructor.name)!.push({event, callback: wrappedCallback});
|
||||
this.emitter.on(event, wrappedCallback as EventBusListener<K>);
|
||||
(this.emitter as events.EventEmitter).on(event, wrappedCallback);
|
||||
}
|
||||
|
||||
public removeListeners(key: ListenerKey): void {
|
||||
@@ -267,7 +268,7 @@ export default class EventBus {
|
||||
|
||||
if (callbacks) {
|
||||
for (const cb of callbacks) {
|
||||
this.emitter.removeListener(cb.event, cb.callback);
|
||||
(this.emitter as events.EventEmitter).removeListener(cb.event, cb.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ export default class Configure extends Extension {
|
||||
logger.info(`Successfully configured '${device.name}' (definition v${definitionVersion})`);
|
||||
device.zh.meta.configured = definitionVersion;
|
||||
device.zh.save();
|
||||
this.eventBus.emitDevicesChanged();
|
||||
this.eventBus.emitExposesAndDevicesChanged(device);
|
||||
} catch (error) {
|
||||
const newAttempts = attempts + 1;
|
||||
this.attempts.set(device.ieeeAddr, newAttempts);
|
||||
|
||||
@@ -176,7 +176,13 @@ export default abstract class ExternalJSExtension<M> extends Extension {
|
||||
}
|
||||
|
||||
const {name, code} = message;
|
||||
|
||||
if (!name.endsWith(".mjs") && !name.endsWith(".js") && !name.endsWith(".cjs")) {
|
||||
return utils.getResponse(message, {}, "JavaScript file must have '.mjs', '.js' or '.cjs' extension");
|
||||
}
|
||||
|
||||
const filePath = this.getFilePath(name, true);
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, code, "utf8");
|
||||
this.symlinkNodeModulesIfNecessary();
|
||||
|
||||
+150
-68
@@ -48,17 +48,31 @@ const GROUP_SUPPORTED_TYPES: ReadonlyArray<string> = ["light", "switch", "lock",
|
||||
const COVER_OPENING_LOOKUP: ReadonlyArray<string> = ["opening", "open", "forward", "up", "rising"];
|
||||
const COVER_CLOSING_LOOKUP: ReadonlyArray<string> = ["closing", "close", "backward", "back", "reverse", "down", "declining"];
|
||||
const COVER_STOPPED_LOOKUP: ReadonlyArray<string> = ["stopped", "stop", "pause", "paused"];
|
||||
const SWITCH_DIFFERENT: ReadonlyArray<string> = ["valve_detection", "window_detection", "auto_lock", "away_mode"];
|
||||
const CONFIG_SWITCH_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
auto_lock: {entity_category: "config", icon: "mdi:lock"},
|
||||
away_mode: {entity_category: "config", icon: "mdi:home-export-outline"},
|
||||
comfort_smiley: {entity_category: "config", icon: "mdi:emoticon-happy-outline"},
|
||||
enable_display: {entity_category: "config", icon: "mdi:monitor"},
|
||||
indicator: {entity_category: "config", icon: "mdi:led-on"},
|
||||
tilt_mode: {entity_category: "config", icon: "mdi:angle-acute"},
|
||||
valve_detection: {entity_category: "config", icon: "mdi:pipe-valve"},
|
||||
window_detection: {entity_category: "config", icon: "mdi:window-open-variant"},
|
||||
} as const;
|
||||
const SWITCH_DIFFERENT: ReadonlyArray<string> = Object.keys(CONFIG_SWITCH_DISCOVERY_LOOKUP);
|
||||
const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
activity_led_indicator: {icon: "mdi:led-on"},
|
||||
area1Occupancy: {device_class: "occupancy"},
|
||||
area2Occupancy: {device_class: "occupancy"},
|
||||
area3Occupancy: {device_class: "occupancy"},
|
||||
area4Occupancy: {device_class: "occupancy"},
|
||||
auto_lock: {entity_category: "config", icon: "mdi:lock"},
|
||||
auto_off: {icon: "mdi:flash-auto"},
|
||||
away_mode: {entity_category: "config", icon: "mdi:home-export-outline"},
|
||||
battery_low: {entity_category: "diagnostic", device_class: "battery"},
|
||||
button_lock: {entity_category: "config", icon: "mdi:lock"},
|
||||
calibration: {entity_category: "config", icon: "mdi:progress-wrench"},
|
||||
calibration_left: {entity_category: "config", icon: "mdi:progress-wrench"},
|
||||
calibration_right: {entity_category: "config", icon: "mdi:progress-wrench"},
|
||||
capabilities_configurable_curve: {entity_category: "diagnostic", icon: "mdi:tune"},
|
||||
capabilities_forward_phase_control: {entity_category: "diagnostic", icon: "mdi:tune"},
|
||||
capabilities_overload_detection: {entity_category: "diagnostic", icon: "mdi:tune"},
|
||||
@@ -71,16 +85,22 @@ const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
consumer_connected: {device_class: "plug"},
|
||||
contact: {device_class: "door"},
|
||||
garage_door_contact: {device_class: "garage_door", payload_on: false, payload_off: true},
|
||||
frost_protection: {entity_category: "config", icon: "mdi:snowflake-thermometer"},
|
||||
heating_stop: {entity_category: "config", icon: "mdi:radiator-off"},
|
||||
eco_mode: {entity_category: "config", icon: "mdi:leaf"},
|
||||
enable_display: {entity_category: "config", icon: "mdi:monitor"},
|
||||
expose_pin: {entity_category: "config", icon: "mdi:pin"},
|
||||
flip_indicator_light: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
gas: {device_class: "gas"},
|
||||
indicator: {entity_category: "config", icon: "mdi:led-on"},
|
||||
indicator_mode: {entity_category: "config", icon: "mdi:led-on"},
|
||||
invert_cover: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
led_disabled_night: {entity_category: "config", icon: "mdi:led-off"},
|
||||
led_indication: {entity_category: "config", icon: "mdi:led-on"},
|
||||
led_enable: {entity_category: "config", icon: "mdi:led-on"},
|
||||
motor_reversal: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
motor_reversal_left: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
motor_reversal_right: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
moving: {device_class: "moving"},
|
||||
no_position_support: {entity_category: "config", icon: "mdi:minus-circle-outline"},
|
||||
noise_detected: {device_class: "sound"},
|
||||
@@ -101,14 +121,15 @@ const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
temperature_scale: {entity_category: "config", icon: "mdi:temperature-celsius"},
|
||||
test: {entity_category: "diagnostic", icon: "mdi:test-tube"},
|
||||
th_heater: {icon: "mdi:heat-wave"},
|
||||
tilt_mode: {entity_category: "config", icon: "mdi:angle-acute"},
|
||||
trigger_indicator: {icon: "mdi:led-on"},
|
||||
valve_alarm: {device_class: "problem"},
|
||||
valve_detection: {icon: "mdi:pipe-valve"},
|
||||
valve_detection: {entity_category: "config", icon: "mdi:pipe-valve"},
|
||||
valve_state: {device_class: "opening"},
|
||||
vibration: {device_class: "vibration"},
|
||||
water_leak: {device_class: "moisture"},
|
||||
window: {device_class: "window"},
|
||||
window_detection: {icon: "mdi:window-open-variant"},
|
||||
window_detection: {entity_category: "config", icon: "mdi:window-open-variant"},
|
||||
window_open: {device_class: "window"},
|
||||
} as const;
|
||||
const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
@@ -120,7 +141,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
alarm_temperature_min: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-low"},
|
||||
angle: {icon: "angle-acute"},
|
||||
angle_axis: {icon: "angle-acute"},
|
||||
apparent_temperature: {icon: "mdi:thermometer-lines", state_class: "measurement"},
|
||||
apparent_temperature: {device_class: "temperature", icon: "mdi:thermometer-lines", preserve_name: true, state_class: "measurement"},
|
||||
aqi: {device_class: "aqi", state_class: "measurement"},
|
||||
auto_relock_time: {entity_category: "config", icon: "mdi:timer"},
|
||||
away_preset_days: {entity_category: "config", icon: "mdi:timer"},
|
||||
@@ -137,8 +158,14 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
boost_time: {entity_category: "config", icon: "mdi:timer"},
|
||||
calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
calibration_time: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
calibration_time_left: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
calibration_time_right: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
co2: {device_class: "carbon_dioxide", state_class: "measurement"},
|
||||
comfort_humidity_max: {device_class: "humidity", entity_category: "config", icon: "mdi:water-percent"},
|
||||
comfort_humidity_min: {device_class: "humidity", entity_category: "config", icon: "mdi:water-percent"},
|
||||
comfort_temperature: {entity_category: "config", icon: "mdi:thermometer"},
|
||||
comfort_temperature_max: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-high"},
|
||||
comfort_temperature_min: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-low"},
|
||||
cpu_temperature: {
|
||||
device_class: "temperature",
|
||||
entity_category: "diagnostic",
|
||||
@@ -149,13 +176,14 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
current_phase_b: {device_class: "current", state_class: "measurement"},
|
||||
current_phase_c: {device_class: "current", state_class: "measurement"},
|
||||
deadzone_temperature: {entity_category: "config", icon: "mdi:thermometer"},
|
||||
detection_delay: {entity_category: "config", icon: "mdi:timer"},
|
||||
detection_interval: {icon: "mdi:timer"},
|
||||
device_temperature: {
|
||||
device_class: "temperature",
|
||||
entity_category: "diagnostic",
|
||||
state_class: "measurement",
|
||||
},
|
||||
dew_point: {icon: "mdi:thermometer-water", state_class: "measurement"},
|
||||
dew_point: {device_class: "temperature", icon: "mdi:thermometer-water", preserve_name: true, state_class: "measurement"},
|
||||
distance: {device_class: "distance", state_class: "measurement"},
|
||||
duration: {entity_category: "config", icon: "mdi:timer"},
|
||||
eco2: {device_class: "volatile_organic_compounds_parts", state_class: "measurement"},
|
||||
@@ -164,14 +192,15 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
external_temperature_input: {device_class: "temperature", icon: "mdi:thermometer"},
|
||||
external_temperature: {device_class: "temperature", icon: "mdi:thermometer", state_class: "measurement"},
|
||||
external_humidity: {device_class: "humidity", icon: "mdi:water-percent", state_class: "measurement"},
|
||||
fading_time: {entity_category: "config", icon: "mdi:timer"},
|
||||
formaldehyd: {state_class: "measurement"},
|
||||
flow: {device_class: "volume_flow_rate", state_class: "measurement"},
|
||||
gas: {device_class: "gas", state_class: "total_increasing", icon: "mdi:meter-gas"},
|
||||
gas_density: {icon: "mdi:google-circles-communities", state_class: "measurement"},
|
||||
gust_speed: {icon: "mdi:weather-windy-variant", state_class: "measurement"},
|
||||
gust_speed: {device_class: "wind_speed", icon: "mdi:weather-windy-variant", preserve_name: true, state_class: "measurement"},
|
||||
hcho: {icon: "mdi:air-filter", state_class: "measurement"},
|
||||
heat_stress: {icon: "mdi:weather-sunny-alert", state_class: "measurement"},
|
||||
humidex: {icon: "mdi:thermometer-alert", state_class: "measurement"},
|
||||
humidex: {device_class: "temperature", icon: "mdi:thermometer-alert", preserve_name: true, state_class: "measurement"},
|
||||
humidity: {device_class: "humidity", state_class: "measurement"},
|
||||
humidity_calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
humidity_max: {entity_category: "config", icon: "mdi:water-percent"},
|
||||
@@ -179,6 +208,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
illuminance_calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
illuminance: {device_class: "illuminance", state_class: "measurement"},
|
||||
illuminance_raw: {state_class: "measurement"},
|
||||
interval_time: {entity_category: "config", icon: "mdi:timer"},
|
||||
internalTemperature: {
|
||||
device_class: "temperature",
|
||||
entity_category: "diagnostic",
|
||||
@@ -192,13 +222,20 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
},
|
||||
load_estimate: {state_class: "measurement"},
|
||||
local_temperature: {device_class: "temperature", state_class: "measurement"},
|
||||
large_motion_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
large_motion_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"},
|
||||
max_range: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
max_temperature: {entity_category: "config", icon: "mdi:thermometer-high"},
|
||||
max_temperature_limit: {entity_category: "config", icon: "mdi:thermometer-high"},
|
||||
maximum_range: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
measurement_interval: {entity_category: "config", icon: "mdi:clock-out"},
|
||||
min_temperature_limit: {entity_category: "config", icon: "mdi:thermometer-low"},
|
||||
min_temperature: {entity_category: "config", icon: "mdi:thermometer-low"},
|
||||
minimum_range: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
minimum_on_level: {entity_category: "config"},
|
||||
measurement_poll_interval: {entity_category: "config", icon: "mdi:clock-out"},
|
||||
medium_motion_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
medium_motion_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"},
|
||||
motion_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"},
|
||||
noise: {device_class: "sound_pressure", state_class: "measurement"},
|
||||
noise_detect_level: {icon: "mdi:volume-equal"},
|
||||
@@ -234,13 +271,21 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
icon: "mdi:brightness-5",
|
||||
},
|
||||
smoke_density: {icon: "mdi:google-circles-communities", state_class: "measurement"},
|
||||
sensitivity: {entity_category: "config", icon: "mdi:tune"},
|
||||
small_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"},
|
||||
small_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"},
|
||||
soil_calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
soil_fertility: {device_class: "conductivity", state_class: "measurement"},
|
||||
soil_moisture: {device_class: "moisture", state_class: "measurement"},
|
||||
soil_sampling: {entity_category: "config", icon: "mdi:clock-out"},
|
||||
soil_warning: {entity_category: "config", icon: "mdi:water-percent-alert"},
|
||||
temperature: {device_class: "temperature", state_class: "measurement"},
|
||||
temperature_probe: {device_class: "temperature", state_class: "measurement"},
|
||||
temperature_calibration: {entity_category: "config", icon: "mdi:wrench-clock"},
|
||||
temperature_max: {entity_category: "config", icon: "mdi:thermometer-plus"},
|
||||
temperature_min: {entity_category: "config", icon: "mdi:thermometer-minus"},
|
||||
temperature_offset: {icon: "mdi:thermometer-lines"},
|
||||
temperature_sampling: {entity_category: "config", icon: "mdi:clock-out"},
|
||||
transition: {entity_category: "config", icon: "mdi:transition"},
|
||||
trigger_count: {icon: "mdi:counter", enabled_by_default: false, state_class: "measurement"},
|
||||
uv_index: {icon: "mdi:white-balance-sunny", state_class: "measurement"},
|
||||
@@ -255,7 +300,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
device_class: "water",
|
||||
state_class: "total_increasing",
|
||||
},
|
||||
wind_chill: {icon: "mdi:snowflake-thermometer", state_class: "measurement"},
|
||||
wind_chill: {device_class: "temperature", icon: "mdi:snowflake-thermometer", preserve_name: true, state_class: "measurement"},
|
||||
wind_direction: {icon: "mdi:compass-outline", state_class: "measurement"},
|
||||
wind_speed: {device_class: "wind_speed", icon: "mdi:weather-windy", state_class: "measurement"},
|
||||
x: {icon: "mdi:axis-x-arrow", state_class: "measurement"},
|
||||
@@ -287,6 +332,7 @@ const ENUM_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
mode_phase_control: {entity_category: "config", icon: "mdi:tune"},
|
||||
mode: {entity_category: "config", icon: "mdi:tune"},
|
||||
mode_switch: {icon: "mdi:tune"},
|
||||
motor_direction: {entity_category: "config", icon: "mdi:arrow-left-right"},
|
||||
motion_sensitivity: {entity_category: "config", icon: "mdi:tune"},
|
||||
operation_mode: {entity_category: "config", icon: "mdi:tune"},
|
||||
power_on_behavior: {entity_category: "config", icon: "mdi:power-settings"},
|
||||
@@ -297,11 +343,13 @@ const ENUM_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
sensitivity: {entity_category: "config", icon: "mdi:tune"},
|
||||
sensor: {icon: "mdi:tune"},
|
||||
sensors_type: {entity_category: "config", icon: "mdi:tune"},
|
||||
set_limits: {entity_category: "config", icon: "mdi:ray-start-end"},
|
||||
sound_volume: {entity_category: "config", icon: "mdi:volume-high"},
|
||||
status: {icon: "mdi:state-machine"},
|
||||
switch_type: {entity_category: "config", icon: "mdi:tune"},
|
||||
temperature_display_mode: {entity_category: "config", icon: "mdi:thermometer"},
|
||||
temperature_sensor_select: {entity_category: "config", icon: "mdi:home-thermometer"},
|
||||
temperature_unit: {entity_category: "config", icon: "mdi:temperature-celsius"},
|
||||
thermostat_unit: {entity_category: "config", icon: "mdi:thermometer"},
|
||||
update: {device_class: "update"},
|
||||
volume: {entity_category: "config", icon: "mdi: volume-high"},
|
||||
@@ -313,7 +361,7 @@ const LIST_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = {
|
||||
color_options: {icon: "mdi:palette"},
|
||||
level_config: {entity_category: "diagnostic"},
|
||||
programming_mode: {icon: "mdi:calendar-clock"},
|
||||
schedule_settings: {icon: "mdi:calendar-clock"},
|
||||
schedule_settings: {entity_category: "config", icon: "mdi:calendar-clock"},
|
||||
} as const;
|
||||
|
||||
const featurePropertyWithoutEndpoint = (feature: zhc.Feature): string => {
|
||||
@@ -324,6 +372,32 @@ const featurePropertyWithoutEndpoint = (feature: zhc.Feature): string => {
|
||||
return feature.property;
|
||||
};
|
||||
|
||||
const applyHomeAssistantExposeMetadata = (payload: DiscoveryEntry, homeAssistant: zhc.Expose["homeassistant"]): void => {
|
||||
if (!homeAssistant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (homeAssistant.type !== undefined) {
|
||||
payload.type = homeAssistant.type;
|
||||
}
|
||||
|
||||
if (homeAssistant.entityCategory !== undefined) {
|
||||
payload.discovery_payload.entity_category = homeAssistant.entityCategory;
|
||||
}
|
||||
|
||||
if (homeAssistant.deviceClass !== undefined) {
|
||||
payload.discovery_payload.device_class = homeAssistant.deviceClass;
|
||||
}
|
||||
|
||||
if (homeAssistant.enabledByDefault !== undefined) {
|
||||
payload.discovery_payload.enabled_by_default = homeAssistant.enabledByDefault;
|
||||
}
|
||||
|
||||
if (homeAssistant.icon !== undefined) {
|
||||
payload.discovery_payload.icon = homeAssistant.icon;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class handles the bridge entity configuration for Home Assistant Discovery.
|
||||
*/
|
||||
@@ -590,7 +664,7 @@ export class HomeAssistant extends Extension {
|
||||
name: endpointName ? utils.capitalize(endpointName) : null,
|
||||
payload_off: state.value_off,
|
||||
payload_on: state.value_on,
|
||||
value_template: `{{ value_json.${property} }}`,
|
||||
value_template: `{{ value_json["${property}"] }}`,
|
||||
command_topic: true,
|
||||
command_topic_prefix: endpointName,
|
||||
},
|
||||
@@ -602,25 +676,26 @@ export class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.state_off = state.value_off;
|
||||
discoveryEntry.discovery_payload.state_on = state.value_on;
|
||||
discoveryEntry.object_id = property;
|
||||
|
||||
if (property === "window_detection") {
|
||||
discoveryEntry.discovery_payload.icon = "mdi:window-open-variant";
|
||||
}
|
||||
Object.assign(discoveryEntry.discovery_payload, CONFIG_SWITCH_DISCOVERY_LOOKUP[property]);
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
break;
|
||||
}
|
||||
case "climate": {
|
||||
const setpointProperties = ["occupied_heating_setpoint", "current_heating_setpoint"];
|
||||
const setpoint = (firstExpose as zhc.Climate).features.filter(isNumericExpose).find((f) => setpointProperties.includes(f.name));
|
||||
const heatingSetpoint = (firstExpose as zhc.Climate).features
|
||||
.filter(isNumericExpose)
|
||||
.find((f) => ["occupied_heating_setpoint", "current_heating_setpoint"].includes(f.name));
|
||||
const coolingSetpoint = (firstExpose as zhc.Climate).features
|
||||
.filter(isNumericExpose)
|
||||
.find((f) => f.name === "occupied_cooling_setpoint");
|
||||
const primarySetpoint = heatingSetpoint ?? coolingSetpoint;
|
||||
assert(
|
||||
setpoint && setpoint.value_min !== undefined && setpoint.value_max !== undefined,
|
||||
primarySetpoint && primarySetpoint.value_min !== undefined && primarySetpoint.value_max !== undefined,
|
||||
"No setpoint found or it is missing value_min/max",
|
||||
);
|
||||
const temperature = (firstExpose as zhc.Climate).features.find((f) => f.name === "local_temperature");
|
||||
assert(temperature, "No temperature found");
|
||||
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: "climate",
|
||||
object_id: endpointName ? `climate_${endpointName}` : "climate",
|
||||
@@ -631,12 +706,12 @@ export class HomeAssistant extends Extension {
|
||||
state_topic: false,
|
||||
temperature_unit: "C",
|
||||
// Setpoint
|
||||
temp_step: setpoint.value_step,
|
||||
min_temp: setpoint.value_min.toString(),
|
||||
max_temp: setpoint.value_max.toString(),
|
||||
temp_step: primarySetpoint.value_step,
|
||||
min_temp: primarySetpoint.value_min.toString(),
|
||||
max_temp: primarySetpoint.value_max.toString(),
|
||||
// Temperature
|
||||
current_temperature_topic: true,
|
||||
current_temperature_template: `{{ value_json.${temperature.property} }}`,
|
||||
current_temperature_template: `{{ value_json["${temperature.property}"] }}`,
|
||||
command_topic_prefix: endpointName,
|
||||
},
|
||||
};
|
||||
@@ -650,7 +725,7 @@ export class HomeAssistant extends Extension {
|
||||
mode.values.splice(mode.values.indexOf("sleep"), 1);
|
||||
}
|
||||
discoveryEntry.discovery_payload.mode_state_topic = true;
|
||||
discoveryEntry.discovery_payload.mode_state_template = `{{ value_json.${mode.property} }}`;
|
||||
discoveryEntry.discovery_payload.mode_state_template = `{{ value_json["${mode.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.modes = mode.values;
|
||||
discoveryEntry.discovery_payload.mode_command_topic = true;
|
||||
}
|
||||
@@ -659,20 +734,19 @@ export class HomeAssistant extends Extension {
|
||||
if (state) {
|
||||
discoveryEntry.mockProperties.push({property: state.property, value: null});
|
||||
discoveryEntry.discovery_payload.action_topic = true;
|
||||
discoveryEntry.discovery_payload.action_template = `{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.${state.property}] }}`;
|
||||
discoveryEntry.discovery_payload.action_template = `{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json["${state.property}"]] }}`;
|
||||
}
|
||||
|
||||
const coolingSetpoint = (firstExpose as zhc.Climate).features.find((f) => f.name === "occupied_cooling_setpoint");
|
||||
if (coolingSetpoint) {
|
||||
discoveryEntry.discovery_payload.temperature_low_command_topic = setpoint.name;
|
||||
discoveryEntry.discovery_payload.temperature_low_state_template = `{{ value_json.${setpoint.property} }}`;
|
||||
if (heatingSetpoint && coolingSetpoint) {
|
||||
discoveryEntry.discovery_payload.temperature_low_command_topic = heatingSetpoint.name;
|
||||
discoveryEntry.discovery_payload.temperature_low_state_template = `{{ value_json["${heatingSetpoint.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.temperature_low_state_topic = true;
|
||||
discoveryEntry.discovery_payload.temperature_high_command_topic = coolingSetpoint.name;
|
||||
discoveryEntry.discovery_payload.temperature_high_state_template = `{{ value_json.${coolingSetpoint.property} }}`;
|
||||
discoveryEntry.discovery_payload.temperature_high_state_template = `{{ value_json["${coolingSetpoint.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.temperature_high_state_topic = true;
|
||||
} else {
|
||||
discoveryEntry.discovery_payload.temperature_command_topic = setpoint.name;
|
||||
discoveryEntry.discovery_payload.temperature_state_template = `{{ value_json.${setpoint.property} }}`;
|
||||
discoveryEntry.discovery_payload.temperature_command_topic = primarySetpoint.name;
|
||||
discoveryEntry.discovery_payload.temperature_state_template = `{{ value_json["${primarySetpoint.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.temperature_state_topic = true;
|
||||
}
|
||||
|
||||
@@ -680,7 +754,7 @@ export class HomeAssistant extends Extension {
|
||||
if (fanMode) {
|
||||
discoveryEntry.discovery_payload.fan_modes = fanMode.values;
|
||||
discoveryEntry.discovery_payload.fan_mode_command_topic = true;
|
||||
discoveryEntry.discovery_payload.fan_mode_state_template = `{{ value_json.${fanMode.property} }}`;
|
||||
discoveryEntry.discovery_payload.fan_mode_state_template = `{{ value_json["${fanMode.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.fan_mode_state_topic = true;
|
||||
}
|
||||
|
||||
@@ -688,7 +762,7 @@ export class HomeAssistant extends Extension {
|
||||
if (swingMode) {
|
||||
discoveryEntry.discovery_payload.swing_modes = swingMode.values;
|
||||
discoveryEntry.discovery_payload.swing_mode_command_topic = true;
|
||||
discoveryEntry.discovery_payload.swing_mode_state_template = `{{ value_json.${swingMode.property} }}`;
|
||||
discoveryEntry.discovery_payload.swing_mode_state_template = `{{ value_json["${swingMode.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.swing_mode_state_topic = true;
|
||||
}
|
||||
|
||||
@@ -696,7 +770,7 @@ export class HomeAssistant extends Extension {
|
||||
if (preset) {
|
||||
discoveryEntry.discovery_payload.preset_modes = preset.values;
|
||||
discoveryEntry.discovery_payload.preset_mode_command_topic = "preset";
|
||||
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json.${preset.property} }}`;
|
||||
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json["${preset.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
|
||||
}
|
||||
|
||||
@@ -710,7 +784,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: tempCalibration.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${tempCalibration.label} ${endpointName}` : tempCalibration.label,
|
||||
value_template: `{{ value_json.${tempCalibration.property} }}`,
|
||||
value_template: `{{ value_json["${tempCalibration.property}"] }}`,
|
||||
command_topic: true,
|
||||
command_topic_prefix: endpointName,
|
||||
command_topic_postfix: tempCalibration.property,
|
||||
@@ -735,7 +809,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: piHeatingDemand.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${piHeatingDemand.label} ${endpointName}` : piHeatingDemand.label,
|
||||
value_template: `{{ value_json.${piHeatingDemand.property} }}`,
|
||||
value_template: `{{ value_json["${piHeatingDemand.property}"] }}`,
|
||||
...(piHeatingDemand.unit && {unit_of_measurement: piHeatingDemand.unit}),
|
||||
icon: "mdi:radiator",
|
||||
},
|
||||
@@ -766,7 +840,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: piCoolingDemand.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? /* v8 ignore next */ `${piCoolingDemand.label} ${endpointName}` : piCoolingDemand.label,
|
||||
value_template: `{{ value_json.${piCoolingDemand.property} }}`,
|
||||
value_template: `{{ value_json["${piCoolingDemand.property}"] }}`,
|
||||
...(piCoolingDemand.unit && {unit_of_measurement: piCoolingDemand.unit}),
|
||||
entity_category: "diagnostic",
|
||||
icon: "mdi:air-conditioner",
|
||||
@@ -788,7 +862,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: localTemperature.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${localTemperature.label} ${endpointName}` : localTemperature.label,
|
||||
value_template: `{{ value_json.${localTemperature.property} }}`,
|
||||
value_template: `{{ value_json["${localTemperature.property}"] }}`,
|
||||
...(localTemperature.unit && {unit_of_measurement: localTemperature.unit}),
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
@@ -800,7 +874,7 @@ export class HomeAssistant extends Extension {
|
||||
|
||||
const currentHumidity = allExposes?.filter(isNumericExpose).find((e) => e.name === "humidity" && e.access & ACCESS_STATE);
|
||||
if (currentHumidity) {
|
||||
discoveryEntry.discovery_payload.current_humidity_template = `{{ value_json.${currentHumidity.property} }}`;
|
||||
discoveryEntry.discovery_payload.current_humidity_template = `{{ value_json["${currentHumidity.property}"] }}`;
|
||||
discoveryEntry.discovery_payload.current_humidity_topic = true;
|
||||
}
|
||||
|
||||
@@ -820,7 +894,7 @@ export class HomeAssistant extends Extension {
|
||||
name: endpointName ? utils.capitalize(endpointName) : null,
|
||||
command_topic_prefix: endpointName,
|
||||
command_topic: true,
|
||||
value_template: `{{ value_json.${state.property} }}`,
|
||||
value_template: `{{ value_json["${state.property}"] }}`,
|
||||
state_locked: state.value_on,
|
||||
state_unlocked: state.value_off,
|
||||
/* v8 ignore next */
|
||||
@@ -863,7 +937,7 @@ export class HomeAssistant extends Extension {
|
||||
// The movement direction is calculated (assumed) in this case.
|
||||
if (running) {
|
||||
assert(position, `Cover must have 'position' when it has 'running'`);
|
||||
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(running)}" in value_json and value_json.${featurePropertyWithoutEndpoint(running)} %} {% if value_json.${featurePropertyWithoutEndpoint(position)} > 0 %} closing {% else %} opening {% endif %} {% else %} stopped {% endif %}`;
|
||||
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(running)}" in value_json and value_json["${featurePropertyWithoutEndpoint(running)}"] %} {% if value_json["${featurePropertyWithoutEndpoint(position)}"] > 0 %} closing {% else %} opening {% endif %} {% else %} stopped {% endif %}`;
|
||||
}
|
||||
|
||||
// If curtains have `motor_state` or `moving` property, lookup for possible
|
||||
@@ -877,13 +951,13 @@ export class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.state_opening = openingState;
|
||||
discoveryEntry.discovery_payload.state_closing = closingState;
|
||||
discoveryEntry.discovery_payload.state_stopped = stoppedState;
|
||||
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(motorState)}" in value_json and value_json.${featurePropertyWithoutEndpoint(motorState)} %} {{ value_json.${featurePropertyWithoutEndpoint(motorState)} }} {% else %} ${stoppedState} {% endif %}`;
|
||||
discoveryEntry.discovery_payload.value_template = `{% if "${featurePropertyWithoutEndpoint(motorState)}" in value_json and value_json["${featurePropertyWithoutEndpoint(motorState)}"] %} {{ value_json["${featurePropertyWithoutEndpoint(motorState)}"] }} {% else %} ${stoppedState} {% endif %}`;
|
||||
}
|
||||
}
|
||||
|
||||
// If curtains do not have `running`, `motor_state` or `moving` properties.
|
||||
if (!discoveryEntry.discovery_payload.value_template) {
|
||||
discoveryEntry.discovery_payload.value_template = `{{ value_json.${featurePropertyWithoutEndpoint(state)} }}`;
|
||||
discoveryEntry.discovery_payload.value_template = `{{ value_json["${featurePropertyWithoutEndpoint(state)}"] }}`;
|
||||
discoveryEntry.discovery_payload.state_open = "OPEN";
|
||||
discoveryEntry.discovery_payload.state_closed = "CLOSE";
|
||||
discoveryEntry.discovery_payload.state_stopped = "STOP";
|
||||
@@ -898,7 +972,7 @@ export class HomeAssistant extends Extension {
|
||||
if (position) {
|
||||
discoveryEntry.discovery_payload = {
|
||||
...discoveryEntry.discovery_payload,
|
||||
position_template: `{{ value_json.${featurePropertyWithoutEndpoint(position)} }}`,
|
||||
position_template: `{{ value_json["${featurePropertyWithoutEndpoint(position)}"] }}`,
|
||||
set_position_template: `{ "${getProperty(position)}": {{ position }} }`,
|
||||
set_position_topic: true,
|
||||
position_topic: true,
|
||||
@@ -910,7 +984,7 @@ export class HomeAssistant extends Extension {
|
||||
...discoveryEntry.discovery_payload,
|
||||
tilt_command_topic: true,
|
||||
tilt_status_topic: true,
|
||||
tilt_status_template: `{{ value_json.${featurePropertyWithoutEndpoint(tilt)} }}`,
|
||||
tilt_status_template: `{{ value_json["${featurePropertyWithoutEndpoint(tilt)}"] }}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -975,14 +1049,14 @@ export class HomeAssistant extends Extension {
|
||||
|
||||
discoveryEntry.discovery_payload.percentage_state_topic = true;
|
||||
discoveryEntry.discovery_payload.percentage_command_topic = "fan_mode";
|
||||
discoveryEntry.discovery_payload.percentage_value_template = `{{ {${percentValues}}[value_json.${modeEmulatedSpeed.property}] | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.percentage_value_template = `{{ {${percentValues}}[value_json["${modeEmulatedSpeed.property}"]] | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.percentage_command_template = `{{ {${percentCommands}}[value] | default('') }}`;
|
||||
discoveryEntry.discovery_payload.speed_range_min = 1;
|
||||
discoveryEntry.discovery_payload.speed_range_max = speeds.length - 1;
|
||||
assert(presets.length !== 0);
|
||||
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
|
||||
discoveryEntry.discovery_payload.preset_mode_command_topic = "fan_mode";
|
||||
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json.${modeEmulatedSpeed.property} if value_json.${modeEmulatedSpeed.property} in [${presetList}] else 'None' | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.preset_mode_value_template = `{{ value_json["${modeEmulatedSpeed.property}"] if value_json["${modeEmulatedSpeed.property}"] in [${presetList}] else 'None' | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.preset_modes = presets;
|
||||
|
||||
// Emulate state based on mode
|
||||
@@ -991,7 +1065,7 @@ export class HomeAssistant extends Extension {
|
||||
} else if (nativeSpeed) {
|
||||
discoveryEntry.discovery_payload.percentage_state_topic = true;
|
||||
discoveryEntry.discovery_payload.percentage_command_topic = "speed";
|
||||
discoveryEntry.discovery_payload.percentage_value_template = `{{ value_json.${nativeSpeed.property} | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.percentage_value_template = `{{ value_json["${nativeSpeed.property}"] | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.percentage_command_template = `{{ value | default('') }}`;
|
||||
discoveryEntry.discovery_payload.speed_range_min = nativeSpeed.value_min;
|
||||
discoveryEntry.discovery_payload.speed_range_max = nativeSpeed.value_max;
|
||||
@@ -1021,8 +1095,8 @@ export class HomeAssistant extends Extension {
|
||||
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
|
||||
value_template:
|
||||
typeof firstExpose.value_on === "boolean"
|
||||
? `{% if value_json.${firstExpose.property} %}true{% else %}false{% endif %}`
|
||||
: `{{ value_json.${firstExpose.property} }}`,
|
||||
? `{% if value_json["${firstExpose.property}"] %}true{% else %}false{% endif %}`
|
||||
: `{{ value_json["${firstExpose.property}"] }}`,
|
||||
payload_on: firstExpose.value_on.toString(),
|
||||
payload_off: firstExpose.value_off.toString(),
|
||||
command_topic: true,
|
||||
@@ -1040,7 +1114,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? /* v8 ignore next */ `${firstExpose.label} ${endpointName}` : firstExpose.label,
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
value_template: `{{ value_json["${firstExpose.property}"] }}`,
|
||||
payload_on: firstExpose.value_on,
|
||||
payload_off: firstExpose.value_off,
|
||||
...(BINARY_DISCOVERY_LOOKUP[firstExpose.name] || {}),
|
||||
@@ -1065,7 +1139,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
value_template: `{{ value_json["${firstExpose.property}"] }}`,
|
||||
command_topic: true,
|
||||
command_topic_prefix: endpointName,
|
||||
command_topic_postfix: firstExpose.property,
|
||||
@@ -1116,7 +1190,7 @@ export class HomeAssistant extends Extension {
|
||||
mockProperties: [{property: firstExpose.property, value: null}],
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
value_template: `{{ value_json["${firstExpose.property}"] }}`,
|
||||
enabled_by_default: !allowsSet,
|
||||
...(firstExpose.unit && {unit_of_measurement: firstExpose.unit}),
|
||||
...NUMERIC_DISCOVERY_LOOKUP[key],
|
||||
@@ -1170,7 +1244,7 @@ export class HomeAssistant extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
const valueTemplate = firstExpose.access & ACCESS_STATE ? `{{ value_json.${firstExpose.property} }}` : undefined;
|
||||
const valueTemplate = firstExpose.access & ACCESS_STATE ? `{{ value_json["${firstExpose.property}"] }}` : undefined;
|
||||
|
||||
/**
|
||||
* If enum has only one item and has SET access then expose as BUTTON entity.
|
||||
@@ -1226,6 +1300,7 @@ export class HomeAssistant extends Extension {
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${firstExpose.label} ${endpointName}` : firstExpose.label,
|
||||
value_template: valueTemplate,
|
||||
...(firstExpose.property === "action" ? {entity_category: "diagnostic"} : {}),
|
||||
...ENUM_DISCOVERY_LOOKUP[firstExpose.name],
|
||||
},
|
||||
});
|
||||
@@ -1299,7 +1374,7 @@ export class HomeAssistant extends Extension {
|
||||
discovery_payload: {
|
||||
name: endpointName ? `${firstExposeTyped.label} ${endpointName}` : firstExposeTyped.label,
|
||||
state_topic: firstExposeTyped.access & ACCESS_STATE,
|
||||
value_template: `{{ value_json.${firstExposeTyped.property} }}`,
|
||||
value_template: `{{ value_json["${firstExposeTyped.property}"] }}`,
|
||||
command_topic_prefix: endpointName,
|
||||
command_topic: true,
|
||||
command_topic_postfix: firstExposeTyped.property,
|
||||
@@ -1317,7 +1392,7 @@ export class HomeAssistant extends Extension {
|
||||
name: endpointName ? `${firstExposeTyped.label} ${endpointName}` : firstExposeTyped.label,
|
||||
// Truncate text if it's too long
|
||||
// https://github.com/Koenkk/zigbee2mqtt/issues/23199
|
||||
value_template: `{{ value_json.${firstExposeTyped.property} | default('',True) | string | truncate(254, True, '', 0) }}`,
|
||||
value_template: `{{ value_json["${firstExposeTyped.property}"] | default('',True) | string | truncate(254, True, '', 0) }}`,
|
||||
...LIST_DISCOVERY_LOOKUP[firstExposeTyped.name],
|
||||
},
|
||||
});
|
||||
@@ -1335,6 +1410,8 @@ export class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
for (const entry of discoveryEntries) {
|
||||
applyHomeAssistantExposeMetadata(entry, firstExpose.homeassistant);
|
||||
|
||||
// If a sensor has entity category `config`, then change
|
||||
// it to `diagnostic`. Sensors have no input, so can't be configured.
|
||||
// https://github.com/Koenkk/zigbee2mqtt/pull/19474
|
||||
@@ -1347,8 +1424,9 @@ export class HomeAssistant extends Extension {
|
||||
delete entry.discovery_payload.entity_category;
|
||||
}
|
||||
|
||||
// Let Home Assistant generate entity name when device_class is present
|
||||
if (entry.discovery_payload.device_class) {
|
||||
// Let Home Assistant generate entity name when device_class is present.
|
||||
// preserve_name allows device_class and explicit name to coexist (e.g. derived sensors).
|
||||
if (entry.discovery_payload.device_class && !NUMERIC_DISCOVERY_LOOKUP[firstExpose.name]?.preserve_name) {
|
||||
delete entry.discovery_payload.name;
|
||||
}
|
||||
|
||||
@@ -1403,7 +1481,7 @@ export class HomeAssistant extends Extension {
|
||||
|
||||
if (match) {
|
||||
const endpoint = match[1];
|
||||
const endpointRegExp = new RegExp(`(.*)_${endpoint}`);
|
||||
const endpointRegExp = new RegExp(`(.*)_${endpoint}$`);
|
||||
const payload: KeyValue = {};
|
||||
for (const key of Object.keys(data.message)) {
|
||||
const keyMatch = endpointRegExp.exec(key);
|
||||
@@ -1814,7 +1892,16 @@ export class HomeAssistant extends Extension {
|
||||
payload.current_humidity_topic = stateTopic;
|
||||
}
|
||||
|
||||
// Override configuration with user settings.
|
||||
if (entity.isDevice()) {
|
||||
try {
|
||||
entity.definition?.meta?.overrideHaDiscoveryPayload?.(payload, entity.options);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to override HA discovery payload (${(error as Error).stack})`);
|
||||
}
|
||||
}
|
||||
|
||||
// Override configuration with user settings after converter compatibility
|
||||
// mappings, so per-device configuration remains the final authority.
|
||||
if (entity.options.homeassistant != null) {
|
||||
const add = (obj: KeyValue, ignoreName: boolean): void => {
|
||||
for (const key in obj) {
|
||||
@@ -1845,14 +1932,6 @@ export class HomeAssistant extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.isDevice()) {
|
||||
try {
|
||||
entity.definition?.meta?.overrideHaDiscoveryPayload?.(payload);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to override HA discovery payload (${(error as Error).stack})`);
|
||||
}
|
||||
}
|
||||
|
||||
const topic = this.getDiscoveryTopic(config, entity);
|
||||
const payloadStr = stringify(payload);
|
||||
newDiscoveredTopics.add(topic);
|
||||
@@ -1938,6 +2017,9 @@ export class HomeAssistant extends Extension {
|
||||
}
|
||||
} else if (data.topic === this.statusTopic && data.message.toLowerCase() === "online") {
|
||||
const timer = setTimeout(async () => {
|
||||
// Re-publish bridge state so HA marks all entities as available before receiving cached device states.
|
||||
await this.mqtt.publish("bridge/state", stringify({state: "online"}), {clientOptions: {retain: true, qos: 1}});
|
||||
|
||||
// Publish all device states.
|
||||
for (const entity of this.zigbee.devicesAndGroupsIterator(utils.deviceNotCoordinator)) {
|
||||
if (this.state.exists(entity)) {
|
||||
|
||||
+29
-12
@@ -50,7 +50,7 @@ function writeFirmwareHexToDataDir(hex: string, fileName: string | undefined, de
|
||||
|
||||
export default class OTAUpdate extends Extension {
|
||||
#topicRegex = new RegExp(
|
||||
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade)?`,
|
||||
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade|abort)?`,
|
||||
"i",
|
||||
);
|
||||
#inProgress = new Set<string>();
|
||||
@@ -248,6 +248,7 @@ export default class OTAUpdate extends Extension {
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check/downgrade"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/downgrade"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/abort"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule/downgrade"]
|
||||
| Zigbee2MQTTAPI["bridge/request/device/ota_update/unschedule"];
|
||||
@@ -258,18 +259,31 @@ export default class OTAUpdate extends Extension {
|
||||
assert(message.id, "Invalid payload");
|
||||
}
|
||||
|
||||
const ID = (messageObject ? message.id : message) as string;
|
||||
const device = this.zigbee.resolveEntity(ID);
|
||||
const id = (messageObject ? message.id : message) as string;
|
||||
const device = this.zigbee.resolveEntity(id);
|
||||
const type = topicMatch[1] as "check" | "update" | "schedule" | "unschedule";
|
||||
const downgrade = topicMatch[2] === "downgrade";
|
||||
const abort = topicMatch[2] === "abort";
|
||||
let error: string | undefined;
|
||||
let errorStack: string | undefined;
|
||||
|
||||
if (!(device instanceof Device)) {
|
||||
error = `Device '${ID}' does not exist`;
|
||||
error = `Device '${id}' does not exist`;
|
||||
} else if (this.#inProgress.has(device.ieeeAddr)) {
|
||||
// also guards against scheduling while check/update op in progress that could result in undesired OTA state
|
||||
error = `OTA update or check for update already in progress for '${device.name}'`;
|
||||
if (abort) {
|
||||
device.zh.abortOta();
|
||||
this.#inProgress.delete(device.ieeeAddr);
|
||||
// cleanup same as a fail
|
||||
this.#removeProgressAndRemainingFromState(device);
|
||||
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "available"));
|
||||
await this.mqtt.publish(
|
||||
"bridge/response/device/ota_update/update/abort",
|
||||
stringify(utils.getResponse<"bridge/response/device/ota_update/update/abort">(message, {id})),
|
||||
);
|
||||
} else {
|
||||
// also guards against scheduling while check/update op in progress that could result in undesired OTA state
|
||||
error = `OTA update or check for update already in progress for '${device.name}'`;
|
||||
}
|
||||
} else {
|
||||
switch (type) {
|
||||
case "check": {
|
||||
@@ -304,7 +318,7 @@ export default class OTAUpdate extends Extension {
|
||||
this.#lastChecked.set(device.ieeeAddr, Date.now());
|
||||
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/check">(message, {
|
||||
id: ID,
|
||||
id,
|
||||
update_available: availableResult.available,
|
||||
downgrade: source.downgrade,
|
||||
source: availableResult.availableMeta?.url,
|
||||
@@ -321,6 +335,11 @@ export default class OTAUpdate extends Extension {
|
||||
}
|
||||
|
||||
case "update": {
|
||||
if (abort) {
|
||||
error = `No OTA in progress to abort for device '${device.name}'`;
|
||||
break;
|
||||
}
|
||||
|
||||
this.#inProgress.add(device.ieeeAddr);
|
||||
|
||||
const otaSettings = settings.get().ota;
|
||||
@@ -378,7 +397,7 @@ export default class OTAUpdate extends Extension {
|
||||
|
||||
const firmwareTo = await this.#readSoftwareBuildIDAndDateCode(device);
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/update">(message, {
|
||||
id: ID,
|
||||
id,
|
||||
from: {
|
||||
file_version: fromVersion,
|
||||
software_build_id: firmwareFrom?.softwareBuildID,
|
||||
@@ -427,7 +446,7 @@ export default class OTAUpdate extends Extension {
|
||||
device.zh.scheduleOta(source);
|
||||
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "scheduled"));
|
||||
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/schedule">(message, {id: ID, url: source.url});
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/schedule">(message, {id, url: source.url});
|
||||
|
||||
await this.mqtt.publish("bridge/response/device/ota_update/schedule", stringify(response));
|
||||
|
||||
@@ -442,9 +461,7 @@ export default class OTAUpdate extends Extension {
|
||||
device.zh.unscheduleOta();
|
||||
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "idle"));
|
||||
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/unschedule">(message, {
|
||||
id: ID,
|
||||
});
|
||||
const response = utils.getResponse<"bridge/response/device/ota_update/unschedule">(message, {id});
|
||||
|
||||
await this.mqtt.publish("bridge/response/device/ota_update/unschedule", stringify(response));
|
||||
|
||||
|
||||
@@ -109,6 +109,11 @@ export default class Mqtt {
|
||||
options.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
if (mqttSettings.server_name) {
|
||||
logger.debug(`MQTT SSL/TLS: SNI server name = ${mqttSettings.server_name}`);
|
||||
options.servername = mqttSettings.server_name;
|
||||
}
|
||||
|
||||
this.client = await connectAsync(mqttSettings.server, options);
|
||||
|
||||
// https://github.com/Koenkk/zigbee2mqtt/issues/9822
|
||||
|
||||
@@ -133,6 +133,7 @@ export interface Zigbee2MQTTSettings {
|
||||
cert?: string;
|
||||
client_id?: string;
|
||||
reject_unauthorized?: boolean;
|
||||
server_name?: string;
|
||||
maximum_packet_size: number;
|
||||
};
|
||||
serial: {
|
||||
@@ -215,6 +216,8 @@ export interface Zigbee2MQTTSettings {
|
||||
timestamp_format: string;
|
||||
output: "json" | "attribute" | "attribute_and_json";
|
||||
transmit_power?: number;
|
||||
/** 3.0: default to false (JSON schema & settings `defaults`) */
|
||||
enable_external_js: boolean;
|
||||
};
|
||||
health: {
|
||||
/** in minutes */
|
||||
@@ -675,6 +678,14 @@ export interface Zigbee2MQTTAPI {
|
||||
default_maximum_data_size?: number | null;
|
||||
};
|
||||
|
||||
"bridge/request/device/ota_update/update/abort": {
|
||||
id: string;
|
||||
};
|
||||
|
||||
"bridge/response/device/ota_update/update/abort": {
|
||||
id: string;
|
||||
};
|
||||
|
||||
"bridge/response/device/ota_update/update": {
|
||||
id: string;
|
||||
from:
|
||||
@@ -999,6 +1010,7 @@ export type Zigbee2MQTTRequestEndpoints =
|
||||
| "bridge/request/device/ota_update/check/downgrade"
|
||||
| "bridge/request/device/ota_update/update"
|
||||
| "bridge/request/device/ota_update/update/downgrade"
|
||||
| "bridge/request/device/ota_update/update/abort"
|
||||
| "bridge/request/device/ota_update/schedule"
|
||||
| "bridge/request/device/ota_update/schedule/downgrade"
|
||||
| "bridge/request/device/ota_update/unschedule"
|
||||
@@ -1048,6 +1060,7 @@ export type Zigbee2MQTTResponseEndpoints =
|
||||
| "bridge/response/device/remove"
|
||||
| "bridge/response/device/ota_update/check"
|
||||
| "bridge/response/device/ota_update/update"
|
||||
| "bridge/response/device/ota_update/update/abort"
|
||||
| "bridge/response/device/ota_update/schedule"
|
||||
| "bridge/response/device/ota_update/unschedule"
|
||||
| "bridge/response/device/interview"
|
||||
|
||||
Vendored
-3
@@ -1,8 +1,5 @@
|
||||
// minimal required because of sub-deps in mqtt >= 5.14.0 to avoid requiring `dom` type
|
||||
declare global {
|
||||
// map to node, doesn't really matter, just needs to be there, and the right "type vs value" to avoid lib check problems
|
||||
/** @deprecated DOM SHIM, DO NOT USE */
|
||||
type MessagePort = import("node:worker_threads").MessagePort;
|
||||
/** @deprecated DOM SHIM, DO NOT USE */
|
||||
type Worker = import("node:worker_threads").Worker;
|
||||
/** @deprecated DOM SHIM, DO NOT USE */
|
||||
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ declare module "zigbee2mqtt-frontend" {
|
||||
export default frontend;
|
||||
}
|
||||
|
||||
declare module "http" {
|
||||
declare module "node:http" {
|
||||
interface IncomingMessage {
|
||||
originalUrl?: string;
|
||||
path?: string;
|
||||
|
||||
@@ -33,12 +33,14 @@
|
||||
"type": "boolean",
|
||||
"title": "Home Assistant legacy action sensors",
|
||||
"description": "Home Assistant legacy actions sensor, when enabled a action sensor will be discoverd and an empty `action` will be send after every published action.",
|
||||
"requiresRestart": true,
|
||||
"default": false
|
||||
},
|
||||
"experimental_event_entities": {
|
||||
"type": "boolean",
|
||||
"title": "Home Assistant experimental event entities",
|
||||
"description": "Home Assistant experimental event entities, when enabled Zigbee2MQTT will add event entities for exposed actions. The events and attributes are currently deemed experimental and subject to change.",
|
||||
"requiresRestart": true,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
@@ -188,6 +190,13 @@
|
||||
"description": "Disable self-signed SSL certificate",
|
||||
"default": true
|
||||
},
|
||||
"server_name": {
|
||||
"type": "string",
|
||||
"title": "TLS server name (SNI)",
|
||||
"requiresRestart": true,
|
||||
"description": "Override the TLS SNI / hostname used for certificate verification when it differs from the host in 'server' (e.g. connecting to an internal service DNS name while validating a public certificate SAN). Leave unset to use the hostname from 'server'.",
|
||||
"examples": ["mqtt.example.com"]
|
||||
},
|
||||
"include_device_information": {
|
||||
"type": "boolean",
|
||||
"title": "Include device information",
|
||||
@@ -808,6 +817,13 @@
|
||||
"title": "MQTT output type",
|
||||
"description": "Examples when 'state' of a device is published json: topic: 'zigbee2mqtt/my_bulb' payload '{\"state\": \"ON\"}' attribute: topic 'zigbee2mqtt/my_bulb/state' payload 'ON' attribute_and_json: both json and attribute (see above)",
|
||||
"default": "json"
|
||||
},
|
||||
"enable_external_js": {
|
||||
"type": "boolean",
|
||||
"title": "Enable external JS",
|
||||
"description": "Enable external JavaScript (extensions and converters) that can execute arbitrary user-provided code. WARNING: If unused, it is advised to disable this.",
|
||||
"default": true,
|
||||
"requiresRestart": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -848,7 +864,8 @@
|
||||
"retain": {
|
||||
"type": "boolean",
|
||||
"title": "Retain",
|
||||
"description": "Retain MQTT messages of this device"
|
||||
"description": "Retain MQTT messages of this device",
|
||||
"default": false
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
@@ -953,10 +970,16 @@
|
||||
"type": "string"
|
||||
},
|
||||
"retain": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"title": "Retain",
|
||||
"description": "Retain MQTT messages of this group",
|
||||
"default": false
|
||||
},
|
||||
"optimistic": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"title": "Optimistic",
|
||||
"description": "Publish the expected state of group members after set",
|
||||
"default": true
|
||||
},
|
||||
"qos": {
|
||||
"type": ["number"],
|
||||
|
||||
@@ -113,6 +113,7 @@ export const defaults = {
|
||||
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
|
||||
timestamp_format: "YYYY-MM-DD HH:mm:ss",
|
||||
output: "json",
|
||||
enable_external_js: true,
|
||||
},
|
||||
health: {
|
||||
interval: 10,
|
||||
@@ -167,6 +168,7 @@ export function writeMinimalDefaults(): void {
|
||||
network_key: "GENERATE",
|
||||
pan_id: "GENERATE",
|
||||
ext_pan_id: "GENERATE",
|
||||
enable_external_js: false,
|
||||
},
|
||||
frontend: {
|
||||
enabled: defaults.frontend.enabled,
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import assert from "node:assert";
|
||||
import fs from "node:fs";
|
||||
|
||||
import equals from "fast-deep-equal/es6";
|
||||
import yaml, {YAMLException} from "js-yaml";
|
||||
import {dump, load, YAMLException} from "js-yaml";
|
||||
|
||||
export class YAMLFileException extends YAMLException {
|
||||
file: string;
|
||||
@@ -20,7 +20,7 @@ export class YAMLFileException extends YAMLException {
|
||||
|
||||
function read(file: string): KeyValue {
|
||||
try {
|
||||
const result = yaml.load(fs.readFileSync(file, "utf8"));
|
||||
const result = load(fs.readFileSync(file, "utf8"));
|
||||
assert(result instanceof Object, `The content of ${file} is expected to be an object`);
|
||||
return result as KeyValue;
|
||||
} catch (error) {
|
||||
@@ -40,7 +40,7 @@ function writeIfChanged(file: string, content: KeyValue): void {
|
||||
const before = readIfExists(file);
|
||||
|
||||
if (!equals(before, content)) {
|
||||
fs.writeFileSync(file, yaml.dump(content));
|
||||
fs.writeFileSync(file, dump(content));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zigbee2mqtt",
|
||||
"version": "2.10.1",
|
||||
"version": "2.12.1-dev",
|
||||
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
|
||||
"main": "index.js",
|
||||
"types": "dist/types/api.d.ts",
|
||||
@@ -10,7 +10,7 @@
|
||||
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.15.0 || ^22.2.0 || ^24"
|
||||
"node": "^22.2.0 || ^24 || ^26"
|
||||
},
|
||||
"keywords": [
|
||||
"xiaomi",
|
||||
@@ -48,37 +48,37 @@
|
||||
"express-static-gzip": "^3.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"finalhandler": "^2.1.1",
|
||||
"humanize-duration": "^3.33.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"humanize-duration": "^3.34.0",
|
||||
"js-yaml": "^5.2.1",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"jszip": "^3.10.1",
|
||||
"mqtt": "^5.15.1",
|
||||
"mqtt": "^5.15.2",
|
||||
"object-assign-deep": "^0.4.0",
|
||||
"rimraf": "^6.1.3",
|
||||
"semver": "^7.7.4",
|
||||
"semver": "^7.8.5",
|
||||
"source-map-support": "^0.5.21",
|
||||
"throttleit": "^2.1.0",
|
||||
"winston": "^3.19.0",
|
||||
"winston-syslog": "^2.7.1",
|
||||
"winston-transport": "^4.9.0",
|
||||
"ws": "^8.20.0",
|
||||
"zigbee-herdsman": "10.0.7",
|
||||
"zigbee-herdsman-converters": "26.46.0",
|
||||
"ws": "^8.21.0",
|
||||
"zigbee-herdsman": "10.6.2",
|
||||
"zigbee-herdsman-converters": "26.81.0",
|
||||
"zigbee2mqtt-frontend": "0.9.21",
|
||||
"zigbee2mqtt-windfront": "2.11.2"
|
||||
"zigbee2mqtt-windfront": "2.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.14",
|
||||
"@biomejs/biome": "^2.5.3",
|
||||
"@types/finalhandler": "^1.2.3",
|
||||
"@types/humanize-duration": "^3.27.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/object-assign-deep": "^0.4.3",
|
||||
"@types/readable-stream": "4.0.23",
|
||||
"@types/readable-stream": "4.0.24",
|
||||
"@types/serve-static": "^2.2.0",
|
||||
"@types/ws": "8.18.1",
|
||||
"@vitest/coverage-v8": "^3.1.1",
|
||||
"tmp": "^0.2.5",
|
||||
"tmp": "^0.2.7",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
|
||||
Generated
+189
-202
@@ -5,7 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
zigbee-herdsman: 10.0.7
|
||||
zigbee-herdsman: 10.6.2
|
||||
|
||||
importers:
|
||||
|
||||
@@ -30,11 +30,11 @@ importers:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
humanize-duration:
|
||||
specifier: ^3.33.2
|
||||
version: 3.33.2
|
||||
specifier: ^3.34.0
|
||||
version: 3.34.0
|
||||
js-yaml:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.1
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
json-stable-stringify-without-jsonify:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
@@ -42,8 +42,8 @@ importers:
|
||||
specifier: ^3.10.1
|
||||
version: 3.10.1
|
||||
mqtt:
|
||||
specifier: ^5.15.1
|
||||
version: 5.15.1
|
||||
specifier: ^5.15.2
|
||||
version: 5.15.2
|
||||
object-assign-deep:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
@@ -51,8 +51,8 @@ importers:
|
||||
specifier: ^6.1.3
|
||||
version: 6.1.3
|
||||
semver:
|
||||
specifier: ^7.7.4
|
||||
version: 7.7.4
|
||||
specifier: ^7.8.5
|
||||
version: 7.8.5
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
@@ -69,24 +69,24 @@ importers:
|
||||
specifier: ^4.9.0
|
||||
version: 4.9.0
|
||||
ws:
|
||||
specifier: ^8.20.0
|
||||
version: 8.20.0
|
||||
specifier: ^8.21.0
|
||||
version: 8.21.0
|
||||
zigbee-herdsman:
|
||||
specifier: 10.0.7
|
||||
version: 10.0.7
|
||||
specifier: 10.6.2
|
||||
version: 10.6.2
|
||||
zigbee-herdsman-converters:
|
||||
specifier: 26.46.0
|
||||
version: 26.46.0
|
||||
specifier: 26.81.0
|
||||
version: 26.81.0
|
||||
zigbee2mqtt-frontend:
|
||||
specifier: 0.9.21
|
||||
version: 0.9.21
|
||||
zigbee2mqtt-windfront:
|
||||
specifier: 2.11.2
|
||||
version: 2.11.2
|
||||
specifier: 2.12.1
|
||||
version: 2.12.1
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: ^2.4.14
|
||||
version: 2.4.14
|
||||
specifier: ^2.5.3
|
||||
version: 2.5.3
|
||||
'@types/finalhandler':
|
||||
specifier: ^1.2.3
|
||||
version: 1.2.4
|
||||
@@ -97,14 +97,14 @@ importers:
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
'@types/node':
|
||||
specifier: ^24.12.2
|
||||
version: 24.12.2
|
||||
specifier: ^26.1.1
|
||||
version: 26.1.1
|
||||
'@types/object-assign-deep':
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.3
|
||||
'@types/readable-stream':
|
||||
specifier: 4.0.23
|
||||
version: 4.0.23
|
||||
specifier: 4.0.24
|
||||
version: 4.0.24
|
||||
'@types/serve-static':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
@@ -113,16 +113,16 @@ importers:
|
||||
version: 8.18.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.1.1
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@24.12.2))
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@26.1.1))
|
||||
tmp:
|
||||
specifier: ^0.2.5
|
||||
version: 0.2.5
|
||||
specifier: ^0.2.7
|
||||
version: 0.2.7
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
vitest:
|
||||
specifier: ^3.1.1
|
||||
version: 3.2.4(@types/node@24.12.2)
|
||||
version: 3.2.4(@types/node@26.1.1)
|
||||
optionalDependencies:
|
||||
unix-dgram:
|
||||
specifier: ^2.0.7
|
||||
@@ -147,8 +147,8 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/runtime@7.29.2':
|
||||
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
|
||||
'@babel/runtime@7.29.7':
|
||||
resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.27.6':
|
||||
@@ -159,55 +159,55 @@ packages:
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@biomejs/biome@2.4.14':
|
||||
resolution: {integrity: sha512-TmAvxOEgrpLypzVGJ8FulIZnlyA9TxrO1hyqYrCz9r+bwma9xXxuLA5IuYnj55XQneFx460KjRbx6SWGLkg3bQ==}
|
||||
'@biomejs/biome@2.5.3':
|
||||
resolution: {integrity: sha512-MrJswFdei9EfDwwUy2tQrPDpK0AO+RmMFvBoaaJ6ayBc3sUbHdCE+XG5N8vp+5So41ZupZJQm0roHFFhMGVD7A==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
hasBin: true
|
||||
|
||||
'@biomejs/cli-darwin-arm64@2.4.14':
|
||||
resolution: {integrity: sha512-XvgoE9XOawUOQPdmvs4J7wPhi/DLwSCGks3AlPJDmh34O0awRTqCED1HRcRDdpf1Zrp4us4MGOOdIxNpbqNF5Q==}
|
||||
'@biomejs/cli-darwin-arm64@2.5.3':
|
||||
resolution: {integrity: sha512-QhYP9muVQ0nUO5zztFuPbEwi4+94sJWVjaZds9aMi1l/KNZBiUjdiSUrGHsTaMGDXrYl+r4AS2sUKfgH3w+V3g==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-darwin-x64@2.4.14':
|
||||
resolution: {integrity: sha512-jE7hKBCFhOx3uUh+ZkWBfOHxAcILPfhFplNkuID/eZeSTLHzfZzoZxW8fbqY9xXRnPi7jGNAf1iPVR+0yWsM/Q==}
|
||||
'@biomejs/cli-darwin-x64@2.5.3':
|
||||
resolution: {integrity: sha512-NC1Ss13UaW7QZX+y8j44bF7AP0jSJdBl6iRhe0MAkvaSqZy+mWg3GaXsrb+eSoHoGDBtaXWEbMVV0iVN2cZ7cQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@2.4.14':
|
||||
resolution: {integrity: sha512-/z+6gqAqqUQTHazwStxSXKHg9b8UvqBmDFRp+c4wYbq2KXhELQDon9EoC9RpmQ8JWkqQx/lIUy/cs+MhzDZp6A==}
|
||||
'@biomejs/cli-linux-arm64-musl@2.5.3':
|
||||
resolution: {integrity: sha512-fccix0w6xp6csCXgxeC0dU/3ecgRQal0y+cv2SP9ajNlhe7Yrk2Ug7UDe2j9AT9ZDYitkXpvUKgZjjuoYeP4Vg==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.4.14':
|
||||
resolution: {integrity: sha512-2TELhZnW5RSLL063l9rc5xLpA0ZIw0Ccwy/0q384rvNAgFw3yI76bd59547yxowdQr5MNPET/xDLrLuvgSeeWQ==}
|
||||
'@biomejs/cli-linux-arm64@2.5.3':
|
||||
resolution: {integrity: sha512-ksx1KWeyYW18ILL04msF/J4ZBtBDN33znYK8Z/aNv/vlBVxL9/g3mGP+omgHJKy4+KWbK87vcmmpmurfNjSgiA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.4.14':
|
||||
resolution: {integrity: sha512-R6BWgJdQOwW9ulJatuTVrQkjnODjqHZkKNOqb1sz++3Noe5LYd0i3PchnOBUCYAPHoPWHhjJqbdZlHEu0hpjdA==}
|
||||
'@biomejs/cli-linux-x64-musl@2.5.3':
|
||||
resolution: {integrity: sha512-O/yU9YKRUiHhmcjF2f38PSjseVk3G4VLWYc0G2HWpzdBVREV6G8IGWIVEFf7MFPfWIzNUIvPsEjeAZQIOgnLcQ==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-linux-x64@2.4.14':
|
||||
resolution: {integrity: sha512-zHrlQZDBDUz4OLAraYpWKcnLS6HOewBFWYOzY91d1ZjdqZwibOyb6BEu6WuWLugyo0P3riCmsbV9UqV1cSXwQg==}
|
||||
'@biomejs/cli-linux-x64@2.5.3':
|
||||
resolution: {integrity: sha512-yMkJtilsgvILDcVkh187aVLTb64xYsrxYajx5kym+r1ULkO5HUOfu9AYKLGQbOVLwJtT2utNw7hhFNg+17mUYA==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.4.14':
|
||||
resolution: {integrity: sha512-M3EH5hqOI/F/FUA2u4xcLoUgmxd218mvuj/6JL7Hv2toQvr2/AdOvKSpGkoRuWFCtQPVa+ZqkEV3Q5xBA9+XSA==}
|
||||
'@biomejs/cli-win32-arm64@2.5.3':
|
||||
resolution: {integrity: sha512-cX5z+GYwRcqEok0AH3KSfQGgqYd0Nomfp6Fbe1uiTtELE38hdH2k842wQ9wLNaF/JJ7r4rjJQ4VR+ce+fRmQbw==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@biomejs/cli-win32-x64@2.4.14':
|
||||
resolution: {integrity: sha512-WL0EG5qE+EAKomGXbf2g6VnSKJhTL3tXC0QRzWRwA5VpjxNYa6H4P7ZWfymbGE4IhZZQi1KXQ2R0YjwInmz2fA==}
|
||||
'@biomejs/cli-win32-x64@2.5.3':
|
||||
resolution: {integrity: sha512-ExSaJWi4/u6+GXCszlSKpWSjKNbDseAYqqkCznsCsZ/4uidZ/BEqsCc5/3ctlq6dfIubdIIRSVLC/PG9xPl70Q==}
|
||||
engines: {node: '>=14.21.3'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -219,8 +219,8 @@ packages:
|
||||
'@dabh/diagnostics@2.0.8':
|
||||
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
|
||||
|
||||
'@date-fns/tz@1.4.1':
|
||||
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
|
||||
'@date-fns/tz@1.5.0':
|
||||
resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
||||
@@ -553,14 +553,14 @@ packages:
|
||||
'@types/js-yaml@4.0.9':
|
||||
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||
|
||||
'@types/node@24.12.2':
|
||||
resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==}
|
||||
'@types/node@26.1.1':
|
||||
resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==}
|
||||
|
||||
'@types/object-assign-deep@0.4.3':
|
||||
resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==}
|
||||
|
||||
'@types/readable-stream@4.0.23':
|
||||
resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==}
|
||||
'@types/readable-stream@4.0.24':
|
||||
resolution: {integrity: sha512-NRvUNC/JFGPJvqdAfEve8oginbM6V08u5NzLWpG8MwA2kTPOLnqk+wpwuPT+mp3aUsxyuT6m2gnrPuHYCruzEg==}
|
||||
|
||||
'@types/serve-static@2.2.0':
|
||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||
@@ -664,18 +664,18 @@ packages:
|
||||
bl@6.1.6:
|
||||
resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==}
|
||||
|
||||
bonjour-service@1.3.0:
|
||||
resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==}
|
||||
bonjour-service@1.4.2:
|
||||
resolution: {integrity: sha512-lMskhnsW70yWHr4PhPeh2rvaIkLSaDpp+nmtbXBZaNKTXwxL73QOkW6HhbzqTImXjevn9TreGT4GACGBCGP9nQ==}
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
|
||||
brace-expansion@2.1.0:
|
||||
resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}
|
||||
|
||||
brace-expansion@5.0.2:
|
||||
resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==}
|
||||
engines: {node: 20 || >=22}
|
||||
brace-expansion@5.0.6:
|
||||
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
|
||||
broker-factory@3.1.14:
|
||||
resolution: {integrity: sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==}
|
||||
broker-factory@3.1.15:
|
||||
resolution: {integrity: sha512-ko+aWvgNuP49meGrdjUu7rC+Y+Wai3cCPxP3xWwHsHfehFjOh5ZQM2yC4gEB2UddeZ/YXhm0K1eG/L6fxym2Og==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
@@ -745,15 +745,6 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -898,11 +889,11 @@ packages:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
humanize-duration@3.33.2:
|
||||
resolution: {integrity: sha512-K7Ny/ULO1hDm2nnhvAY+SJV1skxFb61fd073SG1IWJl+D44ULrruCuTyjHKjBVVcSuTlnY99DKtgEG39CM5QOQ==}
|
||||
humanize-duration@3.34.0:
|
||||
resolution: {integrity: sha512-NDxhYxiiRzsST6xULIHuYWRvsCtviJXRdjarhyWyh78S4Ou+MWhM2k4HQ+y7iegdrzreXe11isd0au1N2PB/pQ==}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
|
||||
iconv-lite@0.7.3:
|
||||
resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
ieee754@1.2.1:
|
||||
@@ -914,8 +905,8 @@ packages:
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
ip-address@10.1.0:
|
||||
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
||||
ip-address@10.2.0:
|
||||
resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
is-fullwidth-code-point@3.0.0:
|
||||
@@ -961,8 +952,8 @@ packages:
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||
js-yaml@5.2.1:
|
||||
resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==}
|
||||
hasBin: true
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
@@ -990,8 +981,8 @@ packages:
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
|
||||
lru-cache@11.2.7:
|
||||
resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
|
||||
lru-cache@11.5.1:
|
||||
resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
||||
magic-string@0.30.17:
|
||||
@@ -1030,8 +1021,8 @@ packages:
|
||||
mqtt-packet@9.0.2:
|
||||
resolution: {integrity: sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==}
|
||||
|
||||
mqtt@5.15.1:
|
||||
resolution: {integrity: sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==}
|
||||
mqtt@5.15.2:
|
||||
resolution: {integrity: sha512-VWZU2CSUY3U3oN0PSBRDE5SNsFi4zqqNeQ/uv3pZWqY3CrBXD/dhd0ZLjlsk5YnebGlrapi4lRVNJPUNQ5aZ3w==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1164,8 +1155,8 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
semver@7.7.4:
|
||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||
semver@7.8.5:
|
||||
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1205,8 +1196,8 @@ packages:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
socks@2.8.7:
|
||||
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
|
||||
socks@2.8.9:
|
||||
resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
@@ -1302,8 +1293,8 @@ packages:
|
||||
resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tmp@0.2.5:
|
||||
resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==}
|
||||
tmp@0.2.7:
|
||||
resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==}
|
||||
engines: {node: '>=14.14'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
@@ -1325,8 +1316,8 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
undici-types@8.3.0:
|
||||
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
|
||||
|
||||
unix-dgram@2.0.6:
|
||||
resolution: {integrity: sha512-AURroAsb73BZ6CdAyMrTk/hYKNj3DuYYEuOaB8bYMOHGKupRNScw90Q5C71tWJc3uE7dIeXRyuwN0xLLq3vDTg==}
|
||||
@@ -1436,17 +1427,17 @@ packages:
|
||||
resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
worker-factory@7.0.49:
|
||||
resolution: {integrity: sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==}
|
||||
worker-factory@7.0.50:
|
||||
resolution: {integrity: sha512-hhwc0G+sFwM4qBuhJIUBn2p1Jf8v/FwmLUANBf/Q+Lt2uI8mfIZQhXaZQACodQD4R7Zp6cn/6702bIvNn2puJQ==}
|
||||
|
||||
worker-timers-broker@8.0.16:
|
||||
resolution: {integrity: sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==}
|
||||
worker-timers-broker@8.0.18:
|
||||
resolution: {integrity: sha512-FrjzDVX1wKfZN0gRbCFqv8VHuTncG4sbI/WGEg4tSSQeIsnwqg4YBYWMAHYLJtUDEYYmiK65UKFrVMVk2irDSg==}
|
||||
|
||||
worker-timers-worker@9.0.14:
|
||||
resolution: {integrity: sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==}
|
||||
worker-timers-worker@9.0.15:
|
||||
resolution: {integrity: sha512-KKUe7lZ/Aignr51H6hOUik8LwTnIgojH/1lwhli8A8qIEIyewogZTpNpMW5B6BF7nmwBOkUoTYgf1H3QShcjSA==}
|
||||
|
||||
worker-timers@8.0.31:
|
||||
resolution: {integrity: sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==}
|
||||
worker-timers@8.0.33:
|
||||
resolution: {integrity: sha512-RQVlKkek80v8M6SHvdMKiywaXFmX3XTpYTXTw0r62PdXB06t74XNxcTuG8wsQwnjorAQymNQyyQ0rzv7PykEMQ==}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
@@ -1456,8 +1447,8 @@ packages:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ws@8.20.0:
|
||||
resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
|
||||
ws@8.21.0:
|
||||
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -1468,12 +1459,12 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
zigbee-herdsman-converters@26.46.0:
|
||||
resolution: {integrity: sha512-RFRQQoxGrz8si/wsFOPMdFXF1ppO0mQxU9cyRvzT98HV4bL3dnUKEWIdo6caCllLn6izA5kdob9vtxXqruU/SQ==}
|
||||
zigbee-herdsman-converters@26.81.0:
|
||||
resolution: {integrity: sha512-jltBt3hlNMfI54RMnX/htM/BbOaXB6Ov0itgmyzxcnZpnMUFZT8Qic484g0INCkMYjUFSXKAXgOEZ/YxPimCCA==}
|
||||
engines: {node: '>=20.15.0'}
|
||||
|
||||
zigbee-herdsman@10.0.7:
|
||||
resolution: {integrity: sha512-QqKJaJryWf7IUuVNV6Dc1/qNPrs2dJANPJhRTG74nZBAsyIDn/vSyCP7g19fuEM7Cphr/0ZgCJZkHP2wKvNPMw==}
|
||||
zigbee-herdsman@10.6.2:
|
||||
resolution: {integrity: sha512-Eiuf4zx833vU/PxiRbOD2yeFqesn3FUfTDOJ7dwqbvTUGgn1LujG+ZLF1GlIWX8S1jZXQTy336gkh7HaCQlTMA==}
|
||||
|
||||
zigbee-on-host@0.2.4:
|
||||
resolution: {integrity: sha512-NIG6CWp+Yfn7PjqEIRvenHqpwT1U7rSkyimnFOUIFpnmxQOJKrmcwWDfn6WjbU/YIYYWSQPlj+g13icu1xwlyg==}
|
||||
@@ -1483,8 +1474,8 @@ packages:
|
||||
resolution: {integrity: sha512-ClHYlG7g0v/tnIsD6kycNCi1ZEKIbQuU7lGJR3WqYcDmuqEMVcHDGbRiRwYRQcZGMxkqCf7guxNsXiKnC10+kg==}
|
||||
engines: {node: '>=20.11'}
|
||||
|
||||
zigbee2mqtt-windfront@2.11.2:
|
||||
resolution: {integrity: sha512-NzuPB8VHZsuumb3euxX73fXtM+neLCgabhBUM1UoVHubF//w9afe6Wuws2e1W59yOSd03lAn9/Bkmt6sgDgjwQ==}
|
||||
zigbee2mqtt-windfront@2.12.1:
|
||||
resolution: {integrity: sha512-uZJdQpCafLyptH3n9/Unhwas/lWt4vXobv2ipzHBaZP89nmRXMoQ3DsYhtDp3U2vpX3wx2tHwDmHIukT7OEugA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
snapshots:
|
||||
@@ -1502,7 +1493,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.27.6
|
||||
|
||||
'@babel/runtime@7.29.2': {}
|
||||
'@babel/runtime@7.29.7': {}
|
||||
|
||||
'@babel/types@7.27.6':
|
||||
dependencies:
|
||||
@@ -1511,39 +1502,39 @@ snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@biomejs/biome@2.4.14':
|
||||
'@biomejs/biome@2.5.3':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 2.4.14
|
||||
'@biomejs/cli-darwin-x64': 2.4.14
|
||||
'@biomejs/cli-linux-arm64': 2.4.14
|
||||
'@biomejs/cli-linux-arm64-musl': 2.4.14
|
||||
'@biomejs/cli-linux-x64': 2.4.14
|
||||
'@biomejs/cli-linux-x64-musl': 2.4.14
|
||||
'@biomejs/cli-win32-arm64': 2.4.14
|
||||
'@biomejs/cli-win32-x64': 2.4.14
|
||||
'@biomejs/cli-darwin-arm64': 2.5.3
|
||||
'@biomejs/cli-darwin-x64': 2.5.3
|
||||
'@biomejs/cli-linux-arm64': 2.5.3
|
||||
'@biomejs/cli-linux-arm64-musl': 2.5.3
|
||||
'@biomejs/cli-linux-x64': 2.5.3
|
||||
'@biomejs/cli-linux-x64-musl': 2.5.3
|
||||
'@biomejs/cli-win32-arm64': 2.5.3
|
||||
'@biomejs/cli-win32-x64': 2.5.3
|
||||
|
||||
'@biomejs/cli-darwin-arm64@2.4.14':
|
||||
'@biomejs/cli-darwin-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@2.4.14':
|
||||
'@biomejs/cli-darwin-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@2.4.14':
|
||||
'@biomejs/cli-linux-arm64-musl@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.4.14':
|
||||
'@biomejs/cli-linux-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.4.14':
|
||||
'@biomejs/cli-linux-x64-musl@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@2.4.14':
|
||||
'@biomejs/cli-linux-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.4.14':
|
||||
'@biomejs/cli-win32-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@2.4.14':
|
||||
'@biomejs/cli-win32-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@colors/colors@1.6.0': {}
|
||||
@@ -1554,7 +1545,7 @@ snapshots:
|
||||
enabled: 2.0.0
|
||||
kuler: 2.0.0
|
||||
|
||||
'@date-fns/tz@1.4.1': {}
|
||||
'@date-fns/tz@1.5.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
optional: true
|
||||
@@ -1766,7 +1757,7 @@ snapshots:
|
||||
|
||||
'@types/finalhandler@1.2.4':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
@@ -1774,33 +1765,33 @@ snapshots:
|
||||
|
||||
'@types/js-yaml@4.0.9': {}
|
||||
|
||||
'@types/node@24.12.2':
|
||||
'@types/node@26.1.1':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
undici-types: 8.3.0
|
||||
|
||||
'@types/object-assign-deep@0.4.3': {}
|
||||
|
||||
'@types/readable-stream@4.0.23':
|
||||
'@types/readable-stream@4.0.24':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/serve-static@2.2.0':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.12.2))':
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@26.1.1))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
ast-v8-to-istanbul: 0.3.3
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
@@ -1810,7 +1801,7 @@ snapshots:
|
||||
std-env: 3.9.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/node@24.12.2)
|
||||
vitest: 3.2.4(@types/node@26.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -1822,13 +1813,13 @@ snapshots:
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.12.2))':
|
||||
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@26.1.1))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.2.4
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.17
|
||||
optionalDependencies:
|
||||
vite: 6.3.5(@types/node@24.12.2)
|
||||
vite: 6.3.5(@types/node@26.1.1)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -1906,30 +1897,30 @@ snapshots:
|
||||
|
||||
bl@6.1.6:
|
||||
dependencies:
|
||||
'@types/readable-stream': 4.0.23
|
||||
'@types/readable-stream': 4.0.24
|
||||
buffer: 6.0.3
|
||||
inherits: 2.0.4
|
||||
readable-stream: 4.7.0
|
||||
|
||||
bonjour-service@1.3.0:
|
||||
bonjour-service@1.4.2:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
multicast-dns: 7.2.5
|
||||
|
||||
brace-expansion@2.0.2:
|
||||
brace-expansion@2.1.0:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
|
||||
brace-expansion@5.0.2:
|
||||
brace-expansion@5.0.6:
|
||||
dependencies:
|
||||
balanced-match: 4.0.2
|
||||
|
||||
broker-factory@3.1.14:
|
||||
broker-factory@3.1.15:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@babel/runtime': 7.29.7
|
||||
fast-unique-numbers: 9.0.27
|
||||
tslib: 2.8.1
|
||||
worker-factory: 7.0.49
|
||||
worker-factory: 7.0.50
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
@@ -1994,10 +1985,6 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -2078,7 +2065,7 @@ snapshots:
|
||||
|
||||
fast-unique-numbers@9.0.27:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@babel/runtime': 7.29.7
|
||||
tslib: 2.8.1
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
@@ -2146,9 +2133,9 @@ snapshots:
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
|
||||
humanize-duration@3.33.2: {}
|
||||
humanize-duration@3.34.0: {}
|
||||
|
||||
iconv-lite@0.7.2:
|
||||
iconv-lite@0.7.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
@@ -2158,7 +2145,7 @@ snapshots:
|
||||
|
||||
inherits@2.0.4: {}
|
||||
|
||||
ip-address@10.1.0: {}
|
||||
ip-address@10.2.0: {}
|
||||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
@@ -2179,7 +2166,7 @@ snapshots:
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -2203,7 +2190,7 @@ snapshots:
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
js-yaml@4.1.1:
|
||||
js-yaml@5.2.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -2237,7 +2224,7 @@ snapshots:
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.2.7: {}
|
||||
lru-cache@11.5.1: {}
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
@@ -2251,7 +2238,7 @@ snapshots:
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.7.4
|
||||
semver: 7.8.5
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
@@ -2261,11 +2248,11 @@ snapshots:
|
||||
|
||||
minimatch@10.2.0:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.2
|
||||
brace-expansion: 5.0.6
|
||||
|
||||
minimatch@9.0.5:
|
||||
dependencies:
|
||||
brace-expansion: 2.0.2
|
||||
brace-expansion: 2.1.0
|
||||
|
||||
minimist@1.2.8: {}
|
||||
|
||||
@@ -2279,9 +2266,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mqtt@5.15.1:
|
||||
mqtt@5.15.2:
|
||||
dependencies:
|
||||
'@types/readable-stream': 4.0.23
|
||||
'@types/readable-stream': 4.0.24
|
||||
'@types/ws': 8.18.1
|
||||
commist: 3.2.0
|
||||
concat-stream: 2.0.0
|
||||
@@ -2293,10 +2280,10 @@ snapshots:
|
||||
number-allocator: 1.0.14
|
||||
readable-stream: 4.7.0
|
||||
rfdc: 1.4.1
|
||||
socks: 2.8.7
|
||||
socks: 2.8.9
|
||||
split2: 4.2.0
|
||||
worker-timers: 8.0.31
|
||||
ws: 8.20.0
|
||||
worker-timers: 8.0.33
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@@ -2350,7 +2337,7 @@ snapshots:
|
||||
|
||||
path-scurry@2.0.1:
|
||||
dependencies:
|
||||
lru-cache: 11.2.7
|
||||
lru-cache: 11.5.1
|
||||
minipass: 7.1.2
|
||||
|
||||
pathe@2.0.3: {}
|
||||
@@ -2440,7 +2427,7 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
semver@7.7.4: {}
|
||||
semver@7.8.5: {}
|
||||
|
||||
send@1.2.1:
|
||||
dependencies:
|
||||
@@ -2485,9 +2472,9 @@ snapshots:
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
socks@2.8.7:
|
||||
socks@2.8.9:
|
||||
dependencies:
|
||||
ip-address: 10.1.0
|
||||
ip-address: 10.2.0
|
||||
smart-buffer: 4.2.0
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
@@ -2572,7 +2559,7 @@ snapshots:
|
||||
|
||||
tinyspy@4.0.3: {}
|
||||
|
||||
tmp@0.2.5: {}
|
||||
tmp@0.2.7: {}
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
@@ -2584,7 +2571,7 @@ snapshots:
|
||||
|
||||
typescript@6.0.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
undici-types@8.3.0: {}
|
||||
|
||||
unix-dgram@2.0.6:
|
||||
dependencies:
|
||||
@@ -2600,13 +2587,13 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite-node@3.2.4(@types/node@24.12.2):
|
||||
vite-node@3.2.4(@types/node@26.1.1):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.3
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.3.5(@types/node@24.12.2)
|
||||
vite: 6.3.5(@types/node@26.1.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -2621,7 +2608,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@6.3.5(@types/node@24.12.2):
|
||||
vite@6.3.5(@types/node@26.1.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
fdir: 6.4.6(picomatch@4.0.2)
|
||||
@@ -2630,21 +2617,21 @@ snapshots:
|
||||
rollup: 4.44.0
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@3.2.4(@types/node@24.12.2):
|
||||
vitest@3.2.4(@types/node@26.1.1):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.2
|
||||
'@vitest/expect': 3.2.4
|
||||
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.12.2))
|
||||
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@26.1.1))
|
||||
'@vitest/pretty-format': 3.2.4
|
||||
'@vitest/runner': 3.2.4
|
||||
'@vitest/snapshot': 3.2.4
|
||||
'@vitest/spy': 3.2.4
|
||||
'@vitest/utils': 3.2.4
|
||||
chai: 5.2.0
|
||||
debug: 4.4.1
|
||||
debug: 4.4.3
|
||||
expect-type: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
@@ -2655,11 +2642,11 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.3.5(@types/node@24.12.2)
|
||||
vite-node: 3.2.4(@types/node@24.12.2)
|
||||
vite: 6.3.5(@types/node@26.1.1)
|
||||
vite-node: 3.2.4(@types/node@26.1.1)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
'@types/node': 26.1.1
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
@@ -2712,32 +2699,32 @@ snapshots:
|
||||
triple-beam: 1.4.1
|
||||
winston-transport: 4.9.0
|
||||
|
||||
worker-factory@7.0.49:
|
||||
worker-factory@7.0.50:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@babel/runtime': 7.29.7
|
||||
fast-unique-numbers: 9.0.27
|
||||
tslib: 2.8.1
|
||||
|
||||
worker-timers-broker@8.0.16:
|
||||
worker-timers-broker@8.0.18:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
broker-factory: 3.1.14
|
||||
'@babel/runtime': 7.29.7
|
||||
broker-factory: 3.1.15
|
||||
fast-unique-numbers: 9.0.27
|
||||
tslib: 2.8.1
|
||||
worker-timers-worker: 9.0.14
|
||||
worker-timers-worker: 9.0.15
|
||||
|
||||
worker-timers-worker@9.0.14:
|
||||
worker-timers-worker@9.0.15:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@babel/runtime': 7.29.7
|
||||
tslib: 2.8.1
|
||||
worker-factory: 7.0.49
|
||||
worker-factory: 7.0.50
|
||||
|
||||
worker-timers@8.0.31:
|
||||
worker-timers@8.0.33:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@babel/runtime': 7.29.7
|
||||
tslib: 2.8.1
|
||||
worker-timers-broker: 8.0.16
|
||||
worker-timers-worker: 9.0.14
|
||||
worker-timers-broker: 8.0.18
|
||||
worker-timers-worker: 9.0.15
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
@@ -2751,23 +2738,23 @@ snapshots:
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.2
|
||||
|
||||
ws@8.20.0: {}
|
||||
ws@8.21.0: {}
|
||||
|
||||
zigbee-herdsman-converters@26.46.0:
|
||||
zigbee-herdsman-converters@26.81.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.7.2
|
||||
semver: 7.7.4
|
||||
zigbee-herdsman: 10.0.7
|
||||
iconv-lite: 0.7.3
|
||||
semver: 7.8.5
|
||||
zigbee-herdsman: 10.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
zigbee-herdsman@10.0.7:
|
||||
zigbee-herdsman@10.6.2:
|
||||
dependencies:
|
||||
'@date-fns/tz': 1.4.1
|
||||
'@date-fns/tz': 1.5.0
|
||||
'@serialport/bindings-cpp': 13.0.1
|
||||
'@serialport/parser-delimiter': 13.0.0
|
||||
'@serialport/stream': 13.0.0
|
||||
bonjour-service: 1.3.0
|
||||
bonjour-service: 1.4.2
|
||||
debounce: 3.0.0
|
||||
fast-deep-equal: 3.1.3
|
||||
slip: 1.0.2
|
||||
@@ -2779,4 +2766,4 @@ snapshots:
|
||||
|
||||
zigbee2mqtt-frontend@0.9.21: {}
|
||||
|
||||
zigbee2mqtt-windfront@2.11.2: {}
|
||||
zigbee2mqtt-windfront@2.12.1: {}
|
||||
|
||||
+13
-23
@@ -1,4 +1,12 @@
|
||||
import {execSync} from "node:child_process";
|
||||
import {execFileSync} from "node:child_process";
|
||||
|
||||
function grepTypeScriptFiles(needle, dir) {
|
||||
try {
|
||||
return execFileSync("grep", ["-r", "-F", "--include=*.ts", "--", needle, dir], {encoding: "utf8"});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkDuplicateIssue(github, context, name) {
|
||||
// Search for existing issues with the same `name`
|
||||
@@ -63,13 +71,7 @@ export async function newDeviceSupport(github, _core, context, zhcDir) {
|
||||
if (tuyaManufacturerNames.length > 0) {
|
||||
for (const [fullName, partialName] of tuyaManufacturerNames) {
|
||||
if (await checkDuplicateIssue(github, context, fullName)) return;
|
||||
const fullMatch = (() => {
|
||||
try {
|
||||
return execSync(`grep -r --include="*.ts" "${fullName}" "${zhcDir}"`, {encoding: "utf8"});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
const fullMatch = grepTypeScriptFiles(fullName, zhcDir);
|
||||
|
||||
console.log(`Checking full match for '${fullName}', result: '${fullMatch}'`);
|
||||
if (fullMatch) {
|
||||
@@ -94,13 +96,7 @@ If you need help with the process, feel free to ask here and we'll be happy to a
|
||||
return;
|
||||
}
|
||||
|
||||
const partialMatch = (() => {
|
||||
try {
|
||||
return execSync(`grep -r --include="*.ts" "${partialName}" "${zhcDir}"`, {encoding: "utf8"});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
const partialMatch = grepTypeScriptFiles(partialName, zhcDir);
|
||||
|
||||
console.log(`Checking partial match for '${partialName}', result: '${partialMatch}'`);
|
||||
if (partialMatch) {
|
||||
@@ -129,14 +125,8 @@ Let us know if it works so we can support this device out-of-the-box!`,
|
||||
|
||||
if (zigbeeModels.length > 0) {
|
||||
for (const zigbeeModel of zigbeeModels) {
|
||||
if (await checkDuplicateIssue(github, context, fullName)) return;
|
||||
const fullMatch = (() => {
|
||||
try {
|
||||
return execSync(`grep -r --include="*.ts" '"${zigbeeModel}"' "${zhcDir}"`, {encoding: "utf8"});
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
if (await checkDuplicateIssue(github, context, zigbeeModel)) return;
|
||||
const fullMatch = grepTypeScriptFiles(`"${zigbeeModel}"`, zhcDir);
|
||||
|
||||
if (fullMatch) {
|
||||
await github.rest.issues.createComment({
|
||||
|
||||
@@ -149,6 +149,7 @@ describe("Controller", () => {
|
||||
user: "user1",
|
||||
client_id: "my_client_id",
|
||||
reject_unauthorized: false,
|
||||
server_name: "mqtt.example.com",
|
||||
version: 5,
|
||||
maximum_packet_size: 20000,
|
||||
};
|
||||
@@ -166,6 +167,7 @@ describe("Controller", () => {
|
||||
username: "user1",
|
||||
clientId: "my_client_id",
|
||||
rejectUnauthorized: false,
|
||||
servername: "mqtt.example.com",
|
||||
protocolVersion: 5,
|
||||
properties: {maximumPacketSize: 20000},
|
||||
};
|
||||
|
||||
+15
-6
@@ -15,12 +15,21 @@ describe("Data", () => {
|
||||
const expected = tmp.dirSync().name;
|
||||
process.env.ZIGBEE2MQTT_DATA = expected;
|
||||
data._testReload();
|
||||
const actual = data.getPath();
|
||||
expect(actual).toBe(expected);
|
||||
expect(data.joinPath("test")).toStrictEqual(path.join(expected, "test"));
|
||||
expect(data.joinPath("/test")).toStrictEqual(path.resolve(expected, "/test"));
|
||||
delete process.env.ZIGBEE2MQTT_DATA;
|
||||
data._testReload();
|
||||
|
||||
try {
|
||||
const actual = data.getPath();
|
||||
expect(actual).toBe(expected);
|
||||
expect(data.joinPath("test")).toStrictEqual(path.join(expected, "test"));
|
||||
expect(data.joinPath("/test")).toStrictEqual(path.resolve(expected, "/test"));
|
||||
} finally {
|
||||
delete process.env.ZIGBEE2MQTT_DATA;
|
||||
data._testReload();
|
||||
}
|
||||
});
|
||||
|
||||
it("Should return data path when joinPath with empty string", () => {
|
||||
const expected = data.getPath();
|
||||
expect(data.joinPath("")).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,6 +144,7 @@ describe("Extension: Bridge", () => {
|
||||
output: "json",
|
||||
pan_id: 6754,
|
||||
timestamp_format: "YYYY-MM-DD HH:mm:ss",
|
||||
enable_external_js: true,
|
||||
},
|
||||
blocklist: [],
|
||||
device_options: {},
|
||||
|
||||
@@ -193,16 +193,28 @@ describe("Extension: Configure", () => {
|
||||
});
|
||||
|
||||
it("Should allow to configure via MQTT", async () => {
|
||||
const emitDevicesChanged = vi.spyOn(controller.eventBus, "emitDevicesChanged");
|
||||
const emitExposesChanged = vi.spyOn(controller.eventBus, "emitExposesChanged");
|
||||
|
||||
mockClear(devices.remote);
|
||||
expectRemoteNotConfigured();
|
||||
await mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/configure", "remote");
|
||||
await flushPromises();
|
||||
expectRemoteConfigured();
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/device/configure",
|
||||
stringify({data: {id: "remote"}, status: "ok"}),
|
||||
{},
|
||||
);
|
||||
try {
|
||||
await mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/configure", "remote");
|
||||
await flushPromises();
|
||||
expectRemoteConfigured();
|
||||
expect(emitDevicesChanged).toHaveBeenCalledTimes(1);
|
||||
expect(emitExposesChanged).toHaveBeenCalledWith({
|
||||
device: controller.zigbee.resolveEntity(devices.remote),
|
||||
});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/device/configure",
|
||||
stringify({data: {id: "remote"}, status: "ok"}),
|
||||
{},
|
||||
);
|
||||
} finally {
|
||||
emitExposesChanged.mockRestore();
|
||||
emitDevicesChanged.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("Fail to configure via MQTT when device does not exist", async () => {
|
||||
|
||||
@@ -488,6 +488,25 @@ describe("Extension: ExternalConverters", () => {
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/converters", stringify([]), {retain: true});
|
||||
});
|
||||
|
||||
it("returns error on invalid name", async () => {
|
||||
const converterName = "foo1";
|
||||
|
||||
await resetExtension();
|
||||
for (const mock of mocksClear) mock.mockClear();
|
||||
|
||||
await (controller.getExtension("ExternalConverters")! as ExternalConverters).onMQTTMessage({
|
||||
topic: "zigbee2mqtt/bridge/request/converter/save",
|
||||
message: {name: converterName, code: "a"},
|
||||
});
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/converter/save",
|
||||
expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`),
|
||||
{},
|
||||
);
|
||||
expect(writeFileSyncSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("returns error on invalid code", async () => {
|
||||
const converterName = "foo1.js";
|
||||
const converterCode = "definetly not a correct javascript code";
|
||||
@@ -609,4 +628,15 @@ describe("Extension: ExternalConverters", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't add extension when external JS disabled", async () => {
|
||||
settings.set(["advanced", "enable_external_js"], false);
|
||||
|
||||
controller = new Controller(vi.fn(), vi.fn());
|
||||
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
expect(controller.getExtension("ExternalConverters")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -299,6 +299,25 @@ describe("Extension: ExternalExtensions", () => {
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/extensions", stringify([]), {retain: true});
|
||||
});
|
||||
|
||||
it("returns error on invalid name", async () => {
|
||||
const extensionName = "foo1";
|
||||
|
||||
await resetExtension();
|
||||
for (const mock of mocksClear) mock.mockClear();
|
||||
|
||||
await (controller.getExtension("ExternalExtensions")! as ExternalExtensions).onMQTTMessage({
|
||||
topic: "zigbee2mqtt/bridge/request/extension/save",
|
||||
message: {name: extensionName, code: "a"},
|
||||
});
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/extension/save",
|
||||
expect.stringContaining(`JavaScript file must have '.mjs', '.js' or '.cjs' extension`),
|
||||
{},
|
||||
);
|
||||
expect(writeFileSyncSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it("returns error on invalid code", async () => {
|
||||
const extensionName = "foo1.js";
|
||||
const extensionCode = "definetly not a correct javascript code";
|
||||
@@ -365,4 +384,15 @@ describe("Extension: ExternalExtensions", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't add extension when external JS disabled", async () => {
|
||||
settings.set(["advanced", "enable_external_js"], false);
|
||||
|
||||
controller = new Controller(vi.fn(), vi.fn());
|
||||
|
||||
await controller.start();
|
||||
await flushPromises();
|
||||
|
||||
expect(controller.getExtension("ExternalExtensions")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -128,6 +128,159 @@ describe("Extension: HomeAssistant", () => {
|
||||
expect(duplicated).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("Should mark thermostat configuration toggles as config entities", () => {
|
||||
const switchExposes = [
|
||||
new zhc.Switch().withLabel("Auto lock").withState("auto_lock", false, "Enable/disable auto lock", zhc.access.STATE_SET, "AUTO", "MANUAL"),
|
||||
new zhc.Switch().withLabel("Away mode").withState("away_mode", false, "Enable/disable away mode", zhc.access.STATE_SET),
|
||||
new zhc.Switch().withLabel("Valve detection").withState("valve_detection", true, "Valve detection", zhc.access.STATE_SET),
|
||||
new zhc.Switch()
|
||||
.withLabel("Window detection")
|
||||
.withState("window_detection", true, "Enables/disables window detection", zhc.access.STATE_SET),
|
||||
];
|
||||
const binaryExposes = [
|
||||
new zhc.Binary("frost_protection", zhc.access.STATE_SET, "ON", "OFF").withDescription("Anti-freeze protection"),
|
||||
new zhc.Binary("heating_stop", zhc.access.STATE_SET, "ON", "OFF").withDescription("Heating stop"),
|
||||
new zhc.Binary("away_mode", zhc.access.STATE_SET, "ON", "OFF").withDescription("Away mode"),
|
||||
new zhc.Binary("window_detection", zhc.access.STATE_SET, "ON", "OFF").withDescription("Open window detection"),
|
||||
];
|
||||
const getDiscoveryConfigs = (expose: zhc.Expose): KeyValueAny[] => {
|
||||
const device = {
|
||||
definition: {},
|
||||
isDevice: (): boolean => true,
|
||||
isGroup: (): boolean => false,
|
||||
endpoint: () => undefined,
|
||||
options: {},
|
||||
exposes: (): zhc.Expose[] => [expose],
|
||||
zh: {endpoints: []},
|
||||
};
|
||||
// @ts-expect-error private method and minimal test device
|
||||
return extension.getConfigs(device);
|
||||
};
|
||||
|
||||
for (const expose of switchExposes) {
|
||||
const [config] = getDiscoveryConfigs(expose);
|
||||
expect(config.type).toStrictEqual("switch");
|
||||
expect(config.object_id).toStrictEqual(expose.features[0].property);
|
||||
expect(config.discovery_payload.entity_category).toStrictEqual("config");
|
||||
expect(config.discovery_payload.command_topic_postfix).toStrictEqual(expose.features[0].property);
|
||||
}
|
||||
|
||||
for (const expose of binaryExposes) {
|
||||
const [config] = getDiscoveryConfigs(expose);
|
||||
expect(config.type).toStrictEqual("switch");
|
||||
expect(config.object_id).toStrictEqual(`switch_${expose.name}`);
|
||||
expect(config.discovery_payload.entity_category).toStrictEqual("config");
|
||||
expect(config.discovery_payload.command_topic_postfix).toStrictEqual(expose.property);
|
||||
}
|
||||
});
|
||||
|
||||
it("Should mark device settings as config entities", () => {
|
||||
const getDiscoveryConfigs = (expose: zhc.Expose): KeyValueAny[] => {
|
||||
const device = {
|
||||
definition: {},
|
||||
isDevice: (): boolean => true,
|
||||
isGroup: (): boolean => false,
|
||||
endpoint: () => undefined,
|
||||
options: {},
|
||||
exposes: (): zhc.Expose[] => [expose],
|
||||
zh: {endpoints: []},
|
||||
};
|
||||
// @ts-expect-error private method and minimal test device
|
||||
return extension.getConfigs(device);
|
||||
};
|
||||
|
||||
const enumExposes = [
|
||||
new zhc.Enum("set_limits", zhc.access.STATE_SET, ["START", "END", "RESET"]),
|
||||
new zhc.Enum("motor_direction", zhc.access.STATE_SET, ["forward", "back"]),
|
||||
new zhc.Enum("temperature_unit", zhc.access.STATE_SET, ["celsius", "fahrenheit"]),
|
||||
];
|
||||
|
||||
for (const expose of enumExposes) {
|
||||
const [config] = getDiscoveryConfigs(expose);
|
||||
expect(config.type).toStrictEqual("select");
|
||||
expect(config.object_id).toStrictEqual(expose.property);
|
||||
expect(config.discovery_payload.entity_category).toStrictEqual("config");
|
||||
}
|
||||
|
||||
const binaryExposes = [
|
||||
new zhc.Binary("tilt_mode", zhc.access.STATE_SET, "ON", "OFF"),
|
||||
new zhc.Binary("calibration_left", zhc.access.STATE_SET, "ON", "OFF"),
|
||||
new zhc.Binary("motor_reversal_right", zhc.access.STATE_SET, "ON", "OFF"),
|
||||
new zhc.Binary("enable_display", zhc.access.STATE_SET, "ON", "OFF"),
|
||||
new zhc.Binary("indicator", zhc.access.STATE_SET, "ON", "OFF"),
|
||||
];
|
||||
|
||||
for (const expose of binaryExposes) {
|
||||
const [config] = getDiscoveryConfigs(expose);
|
||||
expect(config.type).toStrictEqual("switch");
|
||||
expect(config.object_id).toStrictEqual(`switch_${expose.property}`);
|
||||
expect(config.discovery_payload.entity_category).toStrictEqual("config");
|
||||
}
|
||||
|
||||
const numericExposes = [
|
||||
new zhc.Numeric("calibration_time_left", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("comfort_temperature_min", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("comfort_humidity_max", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("measurement_interval", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("minimum_range", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("maximum_range", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("detection_delay", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("fading_time", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("large_motion_detection_sensitivity", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("medium_motion_detection_distance", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("small_detection_sensitivity", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("soil_calibration", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("soil_sampling", zhc.access.STATE_SET),
|
||||
new zhc.Numeric("soil_warning", zhc.access.STATE_SET),
|
||||
];
|
||||
|
||||
for (const expose of numericExposes) {
|
||||
const [config] = getDiscoveryConfigs(expose);
|
||||
expect(config.type).toStrictEqual("number");
|
||||
expect(config.object_id).toStrictEqual(expose.property);
|
||||
expect(config.discovery_payload.entity_category).toStrictEqual("config");
|
||||
}
|
||||
|
||||
const [textConfig] = getDiscoveryConfigs(new zhc.Text("schedule_settings", zhc.access.STATE_SET));
|
||||
expect(textConfig.type).toStrictEqual("text");
|
||||
expect(textConfig.object_id).toStrictEqual("schedule_settings");
|
||||
expect(textConfig.discovery_payload.entity_category).toStrictEqual("config");
|
||||
});
|
||||
|
||||
it("Should apply expose-level Home Assistant discovery metadata", () => {
|
||||
const createDevice = (exposes: zhc.Expose[]): Device =>
|
||||
({
|
||||
definition: {},
|
||||
isDevice: (): boolean => true,
|
||||
isGroup: (): boolean => false,
|
||||
endpoint: () => undefined,
|
||||
options: {},
|
||||
exposes: (): zhc.Expose[] => exposes,
|
||||
zh: {endpoints: []},
|
||||
}) as Device;
|
||||
|
||||
const voltageExpose = new zhc.Numeric("voltage", zhc.access.STATE).withUnit("V");
|
||||
Object.assign(voltageExpose, {
|
||||
homeassistant: {
|
||||
type: "valve",
|
||||
entityCategory: "diagnostic",
|
||||
deviceClass: "voltage",
|
||||
enabledByDefault: false,
|
||||
icon: "mdi:flash",
|
||||
},
|
||||
});
|
||||
|
||||
// @ts-expect-error private
|
||||
const configs = extension.getConfigs(createDevice([voltageExpose]));
|
||||
expect(configs.find((config) => config.object_id === "voltage")?.discovery_payload).toMatchObject({
|
||||
device_class: "voltage",
|
||||
enabled_by_default: false,
|
||||
entity_category: "diagnostic",
|
||||
icon: "mdi:flash",
|
||||
});
|
||||
expect(configs.find((config) => config.object_id === "voltage")?.discovery_payload).not.toHaveProperty("type");
|
||||
});
|
||||
|
||||
it("Should discover devices and groups", async () => {
|
||||
settings.set(["homeassistant", "experimental_event_entities"], true);
|
||||
settings.set(["groups", "9", "homeassistant"], {name: "HA Discovery Group", icon: "mdi:lightbulb-group"});
|
||||
@@ -239,7 +392,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unique_id: "9_switch_zigbee2mqtt",
|
||||
group: ["0x0017880104e45542_switch_right_zigbee2mqtt"],
|
||||
origin: origin,
|
||||
value_template: "{{ value_json.state }}",
|
||||
value_template: '{{ value_json["state"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
@@ -252,7 +405,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -279,7 +432,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.humidity }}",
|
||||
value_template: '{{ value_json["humidity"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_humidity",
|
||||
default_entity_id: "sensor.weather_sensor_humidity",
|
||||
@@ -306,7 +459,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "hPa",
|
||||
device_class: "atmospheric_pressure",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.pressure }}",
|
||||
value_template: '{{ value_json["pressure"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_pressure",
|
||||
default_entity_id: "sensor.weather_sensor_pressure",
|
||||
@@ -333,7 +486,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "battery",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.battery }}",
|
||||
value_template: '{{ value_json["battery"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_battery",
|
||||
default_entity_id: "sensor.weather_sensor_battery",
|
||||
@@ -363,7 +516,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
entity_category: "diagnostic",
|
||||
unit_of_measurement: "lqi",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.linkquality }}",
|
||||
value_template: '{{ value_json["linkquality"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
name: "Linkquality",
|
||||
object_id: "weather_sensor_linkquality",
|
||||
@@ -405,7 +558,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
default_entity_id: "switch.wall_switch_double_left",
|
||||
unique_id: "0x0017880104e45542_switch_left_zigbee2mqtt",
|
||||
origin: origin,
|
||||
value_template: "{{ value_json.state_left }}",
|
||||
value_template: '{{ value_json["state_left"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/switch/0x0017880104e45542/switch_left/config", stringify(payload), {
|
||||
@@ -432,7 +585,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
default_entity_id: "switch.wall_switch_double_right",
|
||||
unique_id: "0x0017880104e45542_switch_right_zigbee2mqtt",
|
||||
origin: origin,
|
||||
value_template: "{{ value_json.state_right }}",
|
||||
value_template: '{{ value_json["state_right"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/switch/0x0017880104e45542/switch_right/config", stringify(payload), {
|
||||
@@ -535,7 +688,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.humidity }}",
|
||||
value_template: '{{ value_json["humidity"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_humidity",
|
||||
default_entity_id: "sensor.weather_sensor_humidity",
|
||||
@@ -607,7 +760,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
enabled_by_default: true,
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -633,7 +786,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.humidity }}",
|
||||
value_template: '{{ value_json["humidity"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_humidity",
|
||||
default_entity_id: "sensor.weather_sensor_humidity",
|
||||
@@ -660,7 +813,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "hPa",
|
||||
device_class: "atmospheric_pressure",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.pressure }}",
|
||||
value_template: '{{ value_json["pressure"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
enabled_by_default: true,
|
||||
object_id: "weather_sensor_pressure",
|
||||
@@ -718,7 +871,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
enabled_by_default: true,
|
||||
object_id: "weather_sensor_temperature",
|
||||
@@ -748,7 +901,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.humidity }}",
|
||||
value_template: '{{ value_json["humidity"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
enabled_by_default: true,
|
||||
device: {
|
||||
@@ -791,7 +944,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -818,7 +971,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "%",
|
||||
device_class: "humidity",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.humidity }}",
|
||||
value_template: '{{ value_json["humidity"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_humidity",
|
||||
default_entity_id: "sensor.weather_sensor_humidity",
|
||||
@@ -881,7 +1034,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
default_entity_id: "light.my_switch",
|
||||
unique_id: "0x0017880104e45541_light_zigbee2mqtt",
|
||||
origin: origin,
|
||||
value_template: "{{ value_json.state }}",
|
||||
value_template: '{{ value_json["state"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/light/0x0017880104e45541/light/config", stringify(payload), {
|
||||
@@ -927,11 +1080,11 @@ describe("Extension: HomeAssistant", () => {
|
||||
command_topic: "zigbee2mqtt/fan/set/fan_state",
|
||||
percentage_state_topic: "zigbee2mqtt/fan",
|
||||
percentage_command_topic: "zigbee2mqtt/fan/set/fan_mode",
|
||||
percentage_value_template: "{{ {'off':0, 'low':1, 'medium':2, 'high':3, 'on':4}[value_json.fan_mode] | default('None') }}",
|
||||
percentage_value_template: "{{ {'off':0, 'low':1, 'medium':2, 'high':3, 'on':4}[value_json[\"fan_mode\"]] | default('None') }}",
|
||||
percentage_command_template: "{{ {0:'off', 1:'low', 2:'medium', 3:'high', 4:'on'}[value] | default('') }}",
|
||||
preset_mode_state_topic: "zigbee2mqtt/fan",
|
||||
preset_mode_command_topic: "zigbee2mqtt/fan/set/fan_mode",
|
||||
preset_mode_value_template: "{{ value_json.fan_mode if value_json.fan_mode in ['smart'] else 'None' | default('None') }}",
|
||||
preset_mode_value_template: "{{ value_json[\"fan_mode\"] if value_json[\"fan_mode\"] in ['smart'] else 'None' | default('None') }}",
|
||||
preset_modes: ["smart"],
|
||||
speed_range_min: 1,
|
||||
speed_range_max: 4,
|
||||
@@ -1003,7 +1156,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
command_topic: "zigbee2mqtt/fanbee/set/state",
|
||||
percentage_state_topic: "zigbee2mqtt/fanbee",
|
||||
percentage_command_topic: "zigbee2mqtt/fanbee/set/speed",
|
||||
percentage_value_template: "{{ value_json.speed | default('None') }}",
|
||||
percentage_value_template: "{{ value_json[\"speed\"] | default('None') }}",
|
||||
percentage_command_template: "{{ value | default('') }}",
|
||||
speed_range_min: 1,
|
||||
speed_range_max: 254,
|
||||
@@ -1036,7 +1189,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
it("Should discover thermostat devices", () => {
|
||||
const payload = {
|
||||
action_template:
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
|
||||
action_topic: "zigbee2mqtt/TS0601_thermostat",
|
||||
availability: [
|
||||
{
|
||||
@@ -1044,7 +1197,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
value_template: "{{ value_json.state }}",
|
||||
},
|
||||
],
|
||||
current_temperature_template: "{{ value_json.local_temperature }}",
|
||||
current_temperature_template: '{{ value_json["local_temperature"] }}',
|
||||
current_temperature_topic: "zigbee2mqtt/TS0601_thermostat",
|
||||
device: {
|
||||
identifiers: ["zigbee2mqtt_0x0017882104a44559"],
|
||||
@@ -1056,18 +1209,18 @@ describe("Extension: HomeAssistant", () => {
|
||||
},
|
||||
preset_mode_command_topic: "zigbee2mqtt/TS0601_thermostat/set/preset",
|
||||
preset_modes: ["schedule", "manual", "boost", "complex", "comfort", "eco", "away"],
|
||||
preset_mode_value_template: "{{ value_json.preset }}",
|
||||
preset_mode_value_template: '{{ value_json["preset"] }}',
|
||||
preset_mode_state_topic: "zigbee2mqtt/TS0601_thermostat",
|
||||
max_temp: "35",
|
||||
min_temp: "5",
|
||||
mode_command_topic: "zigbee2mqtt/TS0601_thermostat/set/system_mode",
|
||||
mode_state_template: "{{ value_json.system_mode }}",
|
||||
mode_state_template: '{{ value_json["system_mode"] }}',
|
||||
mode_state_topic: "zigbee2mqtt/TS0601_thermostat",
|
||||
modes: ["heat", "auto", "off"],
|
||||
name: null,
|
||||
temp_step: 0.5,
|
||||
temperature_command_topic: "zigbee2mqtt/TS0601_thermostat/set/current_heating_setpoint",
|
||||
temperature_state_template: "{{ value_json.current_heating_setpoint }}",
|
||||
temperature_state_template: '{{ value_json["current_heating_setpoint"] }}',
|
||||
temperature_state_topic: "zigbee2mqtt/TS0601_thermostat",
|
||||
temperature_unit: "C",
|
||||
object_id: "ts0601_thermostat",
|
||||
@@ -1107,7 +1260,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_topic: "zigbee2mqtt/thermostat",
|
||||
unique_id: "0x0017880104e45550_pi_heating_demand_zigbee2mqtt",
|
||||
unit_of_measurement: "%",
|
||||
value_template: "{{ value_json.pi_heating_demand }}",
|
||||
value_template: '{{ value_json["pi_heating_demand"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/sensor/0x0017880104e45550/pi_heating_demand/config", stringify(payload), {
|
||||
@@ -1144,7 +1297,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_topic: "zigbee2mqtt/bosch_radiator",
|
||||
unique_id: "0x18fc2600000d7ae2_pi_heating_demand_zigbee2mqtt",
|
||||
unit_of_measurement: "%",
|
||||
value_template: "{{ value_json.pi_heating_demand }}",
|
||||
value_template: '{{ value_json["pi_heating_demand"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/number/0x18fc2600000d7ae2/pi_heating_demand/config", stringify(payload), {
|
||||
@@ -1156,10 +1309,10 @@ describe("Extension: HomeAssistant", () => {
|
||||
it("Should discover Bosch BTH-RA with a compatibility mapping", () => {
|
||||
const payload = {
|
||||
action_template:
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
|
||||
action_topic: "zigbee2mqtt/bosch_radiator",
|
||||
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
|
||||
current_temperature_template: "{{ value_json.local_temperature }}",
|
||||
current_temperature_template: '{{ value_json["local_temperature"] }}',
|
||||
current_temperature_topic: "zigbee2mqtt/bosch_radiator",
|
||||
device: {
|
||||
identifiers: ["zigbee2mqtt_0x18fc2600000d7ae2"],
|
||||
@@ -1184,7 +1337,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
origin: origin,
|
||||
temp_step: 0.5,
|
||||
temperature_command_topic: "zigbee2mqtt/bosch_radiator/set/occupied_heating_setpoint",
|
||||
temperature_state_template: "{{ value_json.occupied_heating_setpoint }}",
|
||||
temperature_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
|
||||
temperature_state_topic: "zigbee2mqtt/bosch_radiator",
|
||||
temperature_unit: "C",
|
||||
unique_id: "0x18fc2600000d7ae2_climate_zigbee2mqtt",
|
||||
@@ -1196,6 +1349,24 @@ describe("Extension: HomeAssistant", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Should apply user configuration after converter compatibility mapping", async () => {
|
||||
settings.set(["devices", "0x18fc2600000d7ae2", "homeassistant", "climate"], {
|
||||
modes: ["off", "heat", "auto"],
|
||||
mode_command_template: null,
|
||||
});
|
||||
|
||||
await resetExtension();
|
||||
await flushPromises();
|
||||
|
||||
const call = mockMQTTPublishAsync.mock.calls.find((c) => c[0] === "homeassistant/climate/0x18fc2600000d7ae2/climate/config");
|
||||
expect(call).toBeDefined();
|
||||
const payload = JSON.parse(call![1] as string);
|
||||
|
||||
expect(payload.modes).toStrictEqual(["off", "heat", "auto"]);
|
||||
expect(payload.mode_command_template).toBeUndefined();
|
||||
expect(payload.mode_command_topic).toStrictEqual("zigbee2mqtt/bosch_radiator/set");
|
||||
});
|
||||
|
||||
it("does not throw when discovery payload override throws", async () => {
|
||||
const bosch = getZ2MEntity(devices["RBSH-TRV0-ZB-EU"]) as Device;
|
||||
assert(typeof bosch.definition?.meta?.overrideHaDiscoveryPayload === "function");
|
||||
@@ -1211,10 +1382,10 @@ describe("Extension: HomeAssistant", () => {
|
||||
|
||||
const payload = {
|
||||
action_template:
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
|
||||
action_topic: "zigbee2mqtt/bosch_radiator",
|
||||
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
|
||||
current_temperature_template: "{{ value_json.local_temperature }}",
|
||||
current_temperature_template: '{{ value_json["local_temperature"] }}',
|
||||
current_temperature_topic: "zigbee2mqtt/bosch_radiator",
|
||||
device: {
|
||||
identifiers: ["zigbee2mqtt_0x18fc2600000d7ae2"],
|
||||
@@ -1228,7 +1399,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
max_temp: "30",
|
||||
min_temp: "5",
|
||||
mode_command_topic: "zigbee2mqtt/bosch_radiator/set/system_mode",
|
||||
mode_state_template: "{{ value_json.system_mode }}",
|
||||
mode_state_template: '{{ value_json["system_mode"] }}',
|
||||
mode_state_topic: "zigbee2mqtt/bosch_radiator",
|
||||
modes: ["heat"],
|
||||
name: null,
|
||||
@@ -1237,7 +1408,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
origin: origin,
|
||||
temp_step: 0.5,
|
||||
temperature_command_topic: "zigbee2mqtt/bosch_radiator/set/occupied_heating_setpoint",
|
||||
temperature_state_template: "{{ value_json.occupied_heating_setpoint }}",
|
||||
temperature_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
|
||||
temperature_state_topic: "zigbee2mqtt/bosch_radiator",
|
||||
temperature_unit: "C",
|
||||
unique_id: "0x18fc2600000d7ae2_climate_zigbee2mqtt",
|
||||
@@ -1252,15 +1423,39 @@ describe("Extension: HomeAssistant", () => {
|
||||
overrideSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("passes device options to discovery payload overrides", async () => {
|
||||
const bosch = getZ2MEntity(devices["RBSH-TRV0-ZB-EU"]) as Device;
|
||||
assert(typeof bosch.definition?.meta?.overrideHaDiscoveryPayload === "function");
|
||||
const overrideSpy = vi.spyOn(bosch.definition.meta, "overrideHaDiscoveryPayload") as MockInstance;
|
||||
settings.set(["devices", "0x18fc2600000d7ae2", "discovery_option_marker"], "passed");
|
||||
|
||||
overrideSpy.mockImplementation((payload, options) => {
|
||||
if (payload.mode_command_topic?.endsWith("/system_mode")) {
|
||||
payload.discovery_option_marker = options?.discovery_option_marker;
|
||||
}
|
||||
});
|
||||
|
||||
await resetExtension();
|
||||
|
||||
expect(overrideSpy).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({discovery_option_marker: "passed"}));
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"homeassistant/climate/0x18fc2600000d7ae2/climate/config",
|
||||
expect.stringContaining('"discovery_option_marker":"passed"'),
|
||||
{qos: 1, retain: true},
|
||||
);
|
||||
|
||||
overrideSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("Should discover Bosch BTH-RM230Z with a current_humidity attribute", () => {
|
||||
const payload = {
|
||||
action_template:
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
|
||||
"{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json[\"running_state\"]] }}",
|
||||
action_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
availability: [{topic: "zigbee2mqtt/bridge/state", value_template: "{{ value_json.state }}"}],
|
||||
current_humidity_template: "{{ value_json.humidity }}",
|
||||
current_humidity_template: '{{ value_json["humidity"] }}',
|
||||
current_humidity_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
current_temperature_template: "{{ value_json.local_temperature }}",
|
||||
current_temperature_template: '{{ value_json["local_temperature"] }}',
|
||||
current_temperature_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
default_entity_id: "climate.bosch_rm230z",
|
||||
device: {
|
||||
@@ -1275,20 +1470,20 @@ describe("Extension: HomeAssistant", () => {
|
||||
min_temp: "5",
|
||||
mode_command_topic: "zigbee2mqtt/bosch_rm230z/set",
|
||||
mode_state_template:
|
||||
"{% set values = {'schedule':'auto','manual':'heat','pause':'off'} %}{% set value = value_json.operating_mode %}{% if value == \"manual\" %}{{ value_json.system_mode }}{% else %}{{ values[value] if value in values.keys() else 'off' }}{% endif %}",
|
||||
"{% set active_modes = ['heat'] %}{% set fallback_mode = 'heat' %}{% set values = {'schedule':'auto','pause':'off'} %}{% set value = value_json.operating_mode %}{% set mode = value_json.system_mode %}{% if value == 'manual' %}{{ mode if mode in active_modes else fallback_mode }}{% else %}{{ values[value] if value in values.keys() else 'off' }}{% endif %}",
|
||||
mode_command_template:
|
||||
"{% set values = { 'auto':'schedule','heat':'manual','cool':'manual','off':'pause'} %}{% if value == \"heat\" or value == \"cool\" %}{\"operating_mode\": \"manual\", \"system_mode\": \"{{ value }}\"}{% else %}{\"operating_mode\": \"{{ values[value] if value in values.keys() else 'pause' }}\"}{% endif %}",
|
||||
"{% set active_modes = ['heat'] %}{% set values = {'auto':'schedule','off':'pause'} %}{% if value in active_modes %}{\"operating_mode\": \"manual\", \"system_mode\": \"{{ value }}\"}{% else %}{\"operating_mode\": \"{{ values[value] if value in values.keys() else 'pause' }}\"}{% endif %}",
|
||||
mode_state_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
modes: ["off", "heat", "cool", "auto"],
|
||||
modes: ["off", "heat", "auto"],
|
||||
name: null,
|
||||
object_id: "bosch_rm230z",
|
||||
origin,
|
||||
temp_step: 0.5,
|
||||
temperature_high_command_topic: "zigbee2mqtt/bosch_rm230z/set/occupied_cooling_setpoint",
|
||||
temperature_high_state_template: "{{ value_json.occupied_cooling_setpoint }}",
|
||||
temperature_high_state_template: '{{ value_json["occupied_cooling_setpoint"] }}',
|
||||
temperature_high_state_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
temperature_low_command_topic: "zigbee2mqtt/bosch_rm230z/set/occupied_heating_setpoint",
|
||||
temperature_low_state_template: "{{ value_json.occupied_heating_setpoint }}",
|
||||
temperature_low_state_template: '{{ value_json["occupied_heating_setpoint"] }}',
|
||||
temperature_low_state_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
temperature_unit: "C",
|
||||
unique_id: "0x18fc2600000d7ae3_climate_zigbee2mqtt",
|
||||
@@ -1319,7 +1514,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_topic: "zigbee2mqtt/bosch_rm230z",
|
||||
unique_id: "0x18fc2600000d7ae3_local_temperature_zigbee2mqtt",
|
||||
unit_of_measurement: "°C",
|
||||
value_template: "{{ value_json.local_temperature }}",
|
||||
value_template: '{{ value_json["local_temperature"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/sensor/0x18fc2600000d7ae3/local_temperature/config", stringify(payload), {
|
||||
@@ -1328,6 +1523,37 @@ describe("Extension: HomeAssistant", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Should discover climate with cooling-only setpoint", () => {
|
||||
const climateExpose = new zhc.Climate()
|
||||
.withSetpoint("occupied_cooling_setpoint", 16, 32, 0.5)
|
||||
.withLocalTemperature()
|
||||
.withSystemMode(["off", "cool", "auto"]);
|
||||
const device = {
|
||||
definition: {},
|
||||
isDevice: (): boolean => true,
|
||||
isGroup: (): boolean => false,
|
||||
endpoint: () => undefined,
|
||||
options: {},
|
||||
exposes: (): zhc.Expose[] => [climateExpose],
|
||||
zh: {endpoints: []},
|
||||
} as Device;
|
||||
|
||||
// @ts-expect-error private
|
||||
const configs = extension.getConfigs(device);
|
||||
const climate = configs.find((c) => c.type === "climate");
|
||||
expect(climate).toBeDefined();
|
||||
expect(climate!.discovery_payload).toMatchObject({
|
||||
temperature_command_topic: "occupied_cooling_setpoint",
|
||||
temperature_state_template: '{{ value_json["occupied_cooling_setpoint"] }}',
|
||||
temperature_state_topic: true,
|
||||
min_temp: "16",
|
||||
max_temp: "32",
|
||||
temp_step: 0.5,
|
||||
});
|
||||
expect(climate!.discovery_payload).not.toHaveProperty("temperature_low_command_topic");
|
||||
expect(climate!.discovery_payload).not.toHaveProperty("temperature_high_command_topic");
|
||||
});
|
||||
|
||||
it("Should discover devices with cover_position", () => {
|
||||
let payload;
|
||||
|
||||
@@ -1336,9 +1562,9 @@ describe("Extension: HomeAssistant", () => {
|
||||
position_topic: "zigbee2mqtt/smart vent",
|
||||
set_position_topic: "zigbee2mqtt/smart vent/set",
|
||||
set_position_template: '{ "position": {{ position }} }',
|
||||
position_template: "{{ value_json.position }}",
|
||||
position_template: '{{ value_json["position"] }}',
|
||||
state_topic: "zigbee2mqtt/smart vent",
|
||||
value_template: "{{ value_json.state }}",
|
||||
value_template: '{{ value_json["state"] }}',
|
||||
state_open: "OPEN",
|
||||
state_closed: "CLOSE",
|
||||
state_stopped: "STOP",
|
||||
@@ -1375,7 +1601,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
via_device: "zigbee2mqtt_bridge_0x00124b00120144ae",
|
||||
},
|
||||
name: "L6",
|
||||
position_template: "{{ value_json.position }}",
|
||||
position_template: '{{ value_json["position"] }}',
|
||||
position_topic: "zigbee2mqtt/zigfred_plus/l6",
|
||||
set_position_template: '{ "position_l6": {{ position }} }',
|
||||
set_position_topic: "zigbee2mqtt/zigfred_plus/l6/set",
|
||||
@@ -1384,13 +1610,13 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_open: "OPEN",
|
||||
state_topic: "zigbee2mqtt/zigfred_plus/l6",
|
||||
tilt_command_topic: "zigbee2mqtt/zigfred_plus/l6/set/tilt",
|
||||
tilt_status_template: "{{ value_json.tilt }}",
|
||||
tilt_status_template: '{{ value_json["tilt"] }}',
|
||||
tilt_status_topic: "zigbee2mqtt/zigfred_plus/l6",
|
||||
object_id: "zigfred_plus_l6",
|
||||
default_entity_id: "cover.zigfred_plus_l6",
|
||||
unique_id: "0xf4ce368a38be56a1_cover_l6_zigbee2mqtt",
|
||||
origin: origin,
|
||||
value_template: "{{ value_json.state }}",
|
||||
value_template: '{{ value_json["state"] }}',
|
||||
};
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/cover/0xf4ce368a38be56a1/cover_l6/config", stringify(payload), {
|
||||
@@ -1420,7 +1646,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
object_id: "0xa4c138018cf95021_left",
|
||||
default_entity_id: "cover.0xa4c138018cf95021_left",
|
||||
origin: origin,
|
||||
position_template: "{{ value_json.position }}",
|
||||
position_template: '{{ value_json["position"] }}',
|
||||
position_topic: "zigbee2mqtt/0xa4c138018cf95021/left",
|
||||
set_position_template: '{ "position_left": {{ position }} }',
|
||||
set_position_topic: "zigbee2mqtt/0xa4c138018cf95021/left/set",
|
||||
@@ -1429,7 +1655,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_stopped: "STOP",
|
||||
state_topic: "zigbee2mqtt/0xa4c138018cf95021/left",
|
||||
unique_id: "0xa4c138018cf95021_cover_left_zigbee2mqtt",
|
||||
value_template: '{% if "moving" in value_json and value_json.moving %} {{ value_json.moving }} {% else %} STOP {% endif %}',
|
||||
value_template: '{% if "moving" in value_json and value_json["moving"] %} {{ value_json["moving"] }} {% else %} STOP {% endif %}',
|
||||
};
|
||||
const payload_right = {
|
||||
availability: [
|
||||
@@ -1451,7 +1677,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
object_id: "0xa4c138018cf95021_right",
|
||||
default_entity_id: "cover.0xa4c138018cf95021_right",
|
||||
origin: origin,
|
||||
position_template: "{{ value_json.position }}",
|
||||
position_template: '{{ value_json["position"] }}',
|
||||
position_topic: "zigbee2mqtt/0xa4c138018cf95021/right",
|
||||
set_position_template: '{ "position_right": {{ position }} }',
|
||||
set_position_topic: "zigbee2mqtt/0xa4c138018cf95021/right/set",
|
||||
@@ -1460,11 +1686,9 @@ describe("Extension: HomeAssistant", () => {
|
||||
state_stopped: "STOP",
|
||||
state_topic: "zigbee2mqtt/0xa4c138018cf95021/right",
|
||||
unique_id: "0xa4c138018cf95021_cover_right_zigbee2mqtt",
|
||||
value_template: '{% if "moving" in value_json and value_json.moving %} {{ value_json.moving }} {% else %} STOP {% endif %}',
|
||||
value_template: '{% if "moving" in value_json and value_json["moving"] %} {{ value_json["moving"] }} {% else %} STOP {% endif %}',
|
||||
};
|
||||
|
||||
console.log(mockMQTTPublishAsync.mock.calls.find((c) => c[0] === "homeassistant/cover/0xa4c138018cf95021/cover_left/config"));
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/cover/0xa4c138018cf95021/cover_left/config", stringify(payload_left), {
|
||||
retain: true,
|
||||
qos: 1,
|
||||
@@ -1483,7 +1707,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
unit_of_measurement: "°C",
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
enabled_by_default: true,
|
||||
object_id: "weather_sensor_temperature",
|
||||
@@ -1669,7 +1893,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
enabled_by_default: true,
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -1724,6 +1948,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
await flushPromises();
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/state", stringify({state: "online"}), {retain: true, qos: 1});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bulb",
|
||||
stringify({
|
||||
@@ -1764,6 +1989,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
await flushPromises();
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await flushPromises();
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bridge/state", stringify({state: "online"}), {retain: true, qos: 1});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bulb",
|
||||
stringify({
|
||||
@@ -1830,7 +2056,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
enabled_by_default: true,
|
||||
state_class: "measurement",
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -1900,7 +2126,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
enabled_by_default: true,
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor_renamed",
|
||||
object_id: "weather_sensor_renamed_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_renamed_temperature",
|
||||
@@ -2034,7 +2260,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
enabled_by_default: true,
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor_renamed",
|
||||
object_id: "weather_sensor_renamed_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_renamed_temperature",
|
||||
@@ -2644,7 +2870,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
device_class: "temperature",
|
||||
state_class: "measurement",
|
||||
enabled_by_default: true,
|
||||
value_template: "{{ value_json.temperature }}",
|
||||
value_template: '{{ value_json["temperature"] }}',
|
||||
state_topic: "zigbee2mqtt/weather_sensor",
|
||||
object_id: "weather_sensor_temperature",
|
||||
default_entity_id: "sensor.weather_sensor_temperature",
|
||||
@@ -3011,7 +3237,7 @@ describe("Extension: HomeAssistant", () => {
|
||||
origin: origin,
|
||||
state_topic: "zigbee2mqtt/0x18fc26000000cafe",
|
||||
unique_id: "0x18fc26000000cafe_device_mode_zigbee2mqtt",
|
||||
value_template: "{{ value_json.device_mode }}",
|
||||
value_template: '{{ value_json["device_mode"] }}',
|
||||
};
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/select/0x18fc26000000cafe/device_mode/config", stringify(payload), {
|
||||
retain: true,
|
||||
@@ -3023,11 +3249,15 @@ describe("Extension: HomeAssistant", () => {
|
||||
settings.set(["homeassistant", "legacy_action_sensor"], true);
|
||||
await resetExtension();
|
||||
|
||||
// Should discovery action sensor
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("homeassistant/sensor/0x0017880104e45520/action/config", expect.any(String), {
|
||||
retain: true,
|
||||
qos: 1,
|
||||
// Should discover action sensor as a diagnostic helper instead of a primary entity.
|
||||
const actionDiscovery = mockMQTTPublishAsync.mock.calls.find(([topic]) => topic === "homeassistant/sensor/0x0017880104e45520/action/config");
|
||||
assert(actionDiscovery);
|
||||
expect(JSON.parse(actionDiscovery[1])).toMatchObject({
|
||||
entity_category: "diagnostic",
|
||||
name: "Action",
|
||||
object_id: "button_action",
|
||||
});
|
||||
expect(actionDiscovery[2]).toStrictEqual({retain: true, qos: 1});
|
||||
|
||||
// Should counter an action payload with an empty payload
|
||||
mockMQTTPublishAsync.mockClear();
|
||||
|
||||
@@ -546,6 +546,71 @@ describe("Extension: OTAUpdate", () => {
|
||||
expect(existsSync(saveFilePath)).toStrictEqual(false);
|
||||
});
|
||||
|
||||
it("aborts running OTA", async () => {
|
||||
let timer: NodeJS.Timeout | undefined;
|
||||
devices.bulb.updateOta.mockImplementationOnce(
|
||||
async (_source, _requestPayload, _requestTsn, _extraMetas, onProgress, _dataSettings, _endpoint) => {
|
||||
onProgress(0, 36000.5678);
|
||||
onProgress(10, 3600.2123);
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
timer = setTimeout(
|
||||
() =>
|
||||
resolve([
|
||||
{
|
||||
...DEFAULT_CURRENT,
|
||||
fileVersion: 1,
|
||||
},
|
||||
{
|
||||
...DEFAULT_CURRENT,
|
||||
fileVersion: 90,
|
||||
},
|
||||
]),
|
||||
10000,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update", stringify({id: "bulb"}));
|
||||
await flushPromises();
|
||||
await vi.advanceTimersByTimeAsync(9000);
|
||||
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update/abort", stringify({id: "bulb"}));
|
||||
await flushPromises();
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bulb",
|
||||
stringify({update: {state: "updating", progress: 0, remaining: 36001}}),
|
||||
{
|
||||
retain: true,
|
||||
qos: 0,
|
||||
},
|
||||
);
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bulb",
|
||||
stringify({update: {state: "updating", progress: 10, remaining: 3600}}),
|
||||
{retain: true, qos: 0},
|
||||
);
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({update: {state: "available"}}), {retain: true, qos: 0});
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/device/ota_update/update/abort",
|
||||
stringify({data: {id: "bulb"}, status: "ok"}),
|
||||
{},
|
||||
);
|
||||
|
||||
clearTimeout(timer);
|
||||
});
|
||||
|
||||
it("handles abort when no running OTA", async () => {
|
||||
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update/abort", stringify({id: "bulb"}));
|
||||
await flushPromises();
|
||||
|
||||
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
|
||||
"zigbee2mqtt/bridge/response/device/ota_update/update",
|
||||
stringify({data: {}, status: "error", error: `No OTA in progress to abort for device 'bulb'`}),
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
it("handles when OTA update fails", async () => {
|
||||
devices.bulb.endpoints[0].read.mockImplementation(() => {
|
||||
return {swBuildId: 1, dateCode: "2019010"};
|
||||
|
||||
@@ -283,6 +283,7 @@ export class Device {
|
||||
unscheduleOta = vi.fn(() => {
|
||||
this.scheduledOta = undefined;
|
||||
});
|
||||
abortOta = vi.fn();
|
||||
scheduledOta: OtaSource | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@@ -345,6 +346,7 @@ export class Device {
|
||||
this.updateOta.mockClear();
|
||||
this.scheduleOta.mockClear();
|
||||
this.unscheduleOta.mockClear();
|
||||
this.abortOta.mockClear();
|
||||
this.meta = {};
|
||||
this.scheduledOta = undefined;
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ const SETTINGS_MINIMAL_DEFAULTS = {
|
||||
network_key: "GENERATE",
|
||||
pan_id: "GENERATE",
|
||||
ext_pan_id: "GENERATE",
|
||||
enable_external_js: false,
|
||||
},
|
||||
frontend: {
|
||||
enabled: settings.defaults.frontend!.enabled,
|
||||
@@ -147,6 +148,7 @@ const SAMPLE_SETTINGS_SAVE = {
|
||||
network_key: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
pan_id: 12345,
|
||||
ext_pan_id: [8, 7, 6, 5, 4, 3, 2, 1],
|
||||
enable_external_js: false,
|
||||
},
|
||||
frontend: {
|
||||
enabled: true,
|
||||
@@ -963,6 +965,7 @@ describe("Onboarding", () => {
|
||||
network_key: "GENERATE",
|
||||
pan_id: "GENERATE",
|
||||
ext_pan_id: "GENERATE",
|
||||
enable_external_js: false,
|
||||
},
|
||||
serial: {
|
||||
port: SAMPLE_SETTINGS_SAVE.serial.port,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {beforeEach, describe, expect, it} from "vitest";
|
||||
import "./mocks/data";
|
||||
|
||||
import fs from "node:fs";
|
||||
import yaml from "js-yaml";
|
||||
import {dump, load} from "js-yaml";
|
||||
import objectAssignDeep from "object-assign-deep";
|
||||
|
||||
import mockedData from "../lib/util/data";
|
||||
@@ -21,14 +21,14 @@ const minimalConfig = {
|
||||
|
||||
describe("Settings", () => {
|
||||
const write = (file: string, json: Record<string, unknown>, reread = true): void => {
|
||||
fs.writeFileSync(file, yaml.dump(json));
|
||||
fs.writeFileSync(file, dump(json));
|
||||
|
||||
if (reread) {
|
||||
settings.reRead();
|
||||
}
|
||||
};
|
||||
|
||||
const read = (file: string): unknown => yaml.load(fs.readFileSync(file, "utf8"));
|
||||
const read = (file: string): unknown => load(fs.readFileSync(file, "utf8"));
|
||||
|
||||
const remove = (file: string): void => {
|
||||
if (fs.existsSync(file)) {
|
||||
|
||||
Reference in New Issue
Block a user