mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-31 08:08:31 +00:00
Compare commits
56
Commits
2.12.0
...
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 |
@@ -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,13 +22,13 @@ 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@v6
|
||||
@@ -216,11 +216,11 @@ 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@v6
|
||||
|
||||
|
||||
@@ -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@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
|
||||
|
||||
@@ -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,7 +13,7 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
ref: dev
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "2.12.0"
|
||||
".": "2.12.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -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'.`);
|
||||
|
||||
+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);
|
||||
|
||||
+120
-38
@@ -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.
|
||||
*/
|
||||
@@ -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,9 +706,9 @@ 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}"] }}`,
|
||||
@@ -662,17 +737,16 @@ export class HomeAssistant extends Extension {
|
||||
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_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;
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
});
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
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",
|
||||
|
||||
+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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zigbee2mqtt",
|
||||
"version": "2.12.0",
|
||||
"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,33 +48,33 @@
|
||||
"express-static-gzip": "^3.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"finalhandler": "^2.1.1",
|
||||
"humanize-duration": "^3.33.2",
|
||||
"js-yaml": "^4.2.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.8.3",
|
||||
"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.21.0",
|
||||
"zigbee-herdsman": "10.4.0",
|
||||
"zigbee-herdsman-converters": "26.63.0",
|
||||
"zigbee-herdsman": "10.6.2",
|
||||
"zigbee-herdsman-converters": "26.81.0",
|
||||
"zigbee2mqtt-frontend": "0.9.21",
|
||||
"zigbee2mqtt-windfront": "2.12.0"
|
||||
"zigbee2mqtt-windfront": "2.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.16",
|
||||
"@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.13.1",
|
||||
"@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",
|
||||
|
||||
Generated
+162
-162
@@ -5,7 +5,7 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
zigbee-herdsman: 10.4.0
|
||||
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.2.0
|
||||
version: 4.2.0
|
||||
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.8.3
|
||||
version: 7.8.3
|
||||
specifier: ^7.8.5
|
||||
version: 7.8.5
|
||||
source-map-support:
|
||||
specifier: ^0.5.21
|
||||
version: 0.5.21
|
||||
@@ -72,21 +72,21 @@ importers:
|
||||
specifier: ^8.21.0
|
||||
version: 8.21.0
|
||||
zigbee-herdsman:
|
||||
specifier: 10.4.0
|
||||
version: 10.4.0
|
||||
specifier: 10.6.2
|
||||
version: 10.6.2
|
||||
zigbee-herdsman-converters:
|
||||
specifier: 26.63.0
|
||||
version: 26.63.0
|
||||
specifier: 26.81.0
|
||||
version: 26.81.0
|
||||
zigbee2mqtt-frontend:
|
||||
specifier: 0.9.21
|
||||
version: 0.9.21
|
||||
zigbee2mqtt-windfront:
|
||||
specifier: 2.12.0
|
||||
version: 2.12.0
|
||||
specifier: 2.12.1
|
||||
version: 2.12.1
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: ^2.4.16
|
||||
version: 2.4.16
|
||||
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.13.1
|
||||
version: 24.13.1
|
||||
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,7 +113,7 @@ importers:
|
||||
version: 8.18.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.1.1
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@24.13.1))
|
||||
version: 3.2.4(vitest@3.2.4(@types/node@26.1.1))
|
||||
tmp:
|
||||
specifier: ^0.2.7
|
||||
version: 0.2.7
|
||||
@@ -122,7 +122,7 @@ importers:
|
||||
version: 6.0.3
|
||||
vitest:
|
||||
specifier: ^3.1.1
|
||||
version: 3.2.4(@types/node@24.13.1)
|
||||
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.16':
|
||||
resolution: {integrity: sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
|
||||
'@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.16':
|
||||
resolution: {integrity: sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==}
|
||||
'@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]
|
||||
@@ -553,14 +553,14 @@ packages:
|
||||
'@types/js-yaml@4.0.9':
|
||||
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||
|
||||
'@types/node@24.13.1':
|
||||
resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==}
|
||||
'@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,8 +664,8 @@ packages:
|
||||
bl@6.1.6:
|
||||
resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==}
|
||||
|
||||
bonjour-service@1.4.0:
|
||||
resolution: {integrity: sha512-fGQtj1qdR9vIKjFiWPQd52qIqwjaYqhcI40JEiDuvlZ86E7ZBPBwY9fPgHy9r2rYGIjiRfctNPYz6OQU73ww2w==}
|
||||
bonjour-service@1.4.2:
|
||||
resolution: {integrity: sha512-lMskhnsW70yWHr4PhPeh2rvaIkLSaDpp+nmtbXBZaNKTXwxL73QOkW6HhbzqTImXjevn9TreGT4GACGBCGP9nQ==}
|
||||
|
||||
brace-expansion@2.1.0:
|
||||
resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}
|
||||
@@ -674,8 +674,8 @@ packages:
|
||||
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==}
|
||||
@@ -889,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:
|
||||
@@ -905,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:
|
||||
@@ -952,8 +952,8 @@ packages:
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
js-yaml@4.2.0:
|
||||
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
|
||||
js-yaml@5.2.1:
|
||||
resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==}
|
||||
hasBin: true
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
@@ -981,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:
|
||||
@@ -1021,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
|
||||
|
||||
@@ -1155,8 +1155,8 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
semver@7.8.3:
|
||||
resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==}
|
||||
semver@7.8.5:
|
||||
resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1196,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:
|
||||
@@ -1316,8 +1316,8 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.18.2:
|
||||
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||
undici-types@8.3.0:
|
||||
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
|
||||
|
||||
unix-dgram@2.0.6:
|
||||
resolution: {integrity: sha512-AURroAsb73BZ6CdAyMrTk/hYKNj3DuYYEuOaB8bYMOHGKupRNScw90Q5C71tWJc3uE7dIeXRyuwN0xLLq3vDTg==}
|
||||
@@ -1427,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==}
|
||||
@@ -1459,12 +1459,12 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
zigbee-herdsman-converters@26.63.0:
|
||||
resolution: {integrity: sha512-5Hg4CNeu2PW0IZ+GsFtf8rqdMA8WrIMx+XZtpL4QWAOx5WpuzbyuFPseZKbs4iswtuf/zzPFZrGt07sx83srRg==}
|
||||
zigbee-herdsman-converters@26.81.0:
|
||||
resolution: {integrity: sha512-jltBt3hlNMfI54RMnX/htM/BbOaXB6Ov0itgmyzxcnZpnMUFZT8Qic484g0INCkMYjUFSXKAXgOEZ/YxPimCCA==}
|
||||
engines: {node: '>=20.15.0'}
|
||||
|
||||
zigbee-herdsman@10.4.0:
|
||||
resolution: {integrity: sha512-LO0LV9Qibgv4GuEoDjwZa1No18owetv0iQ1vZZ06rbv+SOUsg8uILBTCv5+S7PKYUYF4krVJH32x2qXYu02r5Q==}
|
||||
zigbee-herdsman@10.6.2:
|
||||
resolution: {integrity: sha512-Eiuf4zx833vU/PxiRbOD2yeFqesn3FUfTDOJ7dwqbvTUGgn1LujG+ZLF1GlIWX8S1jZXQTy336gkh7HaCQlTMA==}
|
||||
|
||||
zigbee-on-host@0.2.4:
|
||||
resolution: {integrity: sha512-NIG6CWp+Yfn7PjqEIRvenHqpwT1U7rSkyimnFOUIFpnmxQOJKrmcwWDfn6WjbU/YIYYWSQPlj+g13icu1xwlyg==}
|
||||
@@ -1474,8 +1474,8 @@ packages:
|
||||
resolution: {integrity: sha512-ClHYlG7g0v/tnIsD6kycNCi1ZEKIbQuU7lGJR3WqYcDmuqEMVcHDGbRiRwYRQcZGMxkqCf7guxNsXiKnC10+kg==}
|
||||
engines: {node: '>=20.11'}
|
||||
|
||||
zigbee2mqtt-windfront@2.12.0:
|
||||
resolution: {integrity: sha512-sDwgEFlmKMj9Sl9FLhfZPkYEd7185v4spwxYXZl7gwQR3xrxeWmnLHtpQ3DfTJd2Ns6V6hzC80Sp8EmB2z8KKg==}
|
||||
zigbee2mqtt-windfront@2.12.1:
|
||||
resolution: {integrity: sha512-uZJdQpCafLyptH3n9/Unhwas/lWt4vXobv2ipzHBaZP89nmRXMoQ3DsYhtDp3U2vpX3wx2tHwDmHIukT7OEugA==}
|
||||
engines: {node: '>=22.12.0'}
|
||||
|
||||
snapshots:
|
||||
@@ -1493,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:
|
||||
@@ -1502,39 +1502,39 @@ snapshots:
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@biomejs/biome@2.4.16':
|
||||
'@biomejs/biome@2.5.3':
|
||||
optionalDependencies:
|
||||
'@biomejs/cli-darwin-arm64': 2.4.16
|
||||
'@biomejs/cli-darwin-x64': 2.4.16
|
||||
'@biomejs/cli-linux-arm64': 2.4.16
|
||||
'@biomejs/cli-linux-arm64-musl': 2.4.16
|
||||
'@biomejs/cli-linux-x64': 2.4.16
|
||||
'@biomejs/cli-linux-x64-musl': 2.4.16
|
||||
'@biomejs/cli-win32-arm64': 2.4.16
|
||||
'@biomejs/cli-win32-x64': 2.4.16
|
||||
'@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.16':
|
||||
'@biomejs/cli-darwin-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-darwin-x64@2.4.16':
|
||||
'@biomejs/cli-darwin-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64-musl@2.4.16':
|
||||
'@biomejs/cli-linux-arm64-musl@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-arm64@2.4.16':
|
||||
'@biomejs/cli-linux-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64-musl@2.4.16':
|
||||
'@biomejs/cli-linux-x64-musl@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-linux-x64@2.4.16':
|
||||
'@biomejs/cli-linux-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-arm64@2.4.16':
|
||||
'@biomejs/cli-win32-arm64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@biomejs/cli-win32-x64@2.4.16':
|
||||
'@biomejs/cli-win32-x64@2.5.3':
|
||||
optional: true
|
||||
|
||||
'@colors/colors@1.6.0': {}
|
||||
@@ -1757,7 +1757,7 @@ snapshots:
|
||||
|
||||
'@types/finalhandler@1.2.4':
|
||||
dependencies:
|
||||
'@types/node': 24.13.1
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
@@ -1765,28 +1765,28 @@ snapshots:
|
||||
|
||||
'@types/js-yaml@4.0.9': {}
|
||||
|
||||
'@types/node@24.13.1':
|
||||
'@types/node@26.1.1':
|
||||
dependencies:
|
||||
undici-types: 7.18.2
|
||||
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.13.1
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/serve-static@2.2.0':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 24.13.1
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 24.13.1
|
||||
'@types/node': 26.1.1
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.13.1))':
|
||||
'@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
|
||||
@@ -1801,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.13.1)
|
||||
vitest: 3.2.4(@types/node@26.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -1813,13 +1813,13 @@ snapshots:
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.13.1))':
|
||||
'@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.13.1)
|
||||
vite: 6.3.5(@types/node@26.1.1)
|
||||
|
||||
'@vitest/pretty-format@3.2.4':
|
||||
dependencies:
|
||||
@@ -1897,12 +1897,12 @@ 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.4.0:
|
||||
bonjour-service@1.4.2:
|
||||
dependencies:
|
||||
fast-deep-equal: 3.1.3
|
||||
multicast-dns: 7.2.5
|
||||
@@ -1915,12 +1915,12 @@ snapshots:
|
||||
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: {}
|
||||
|
||||
@@ -2065,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: {}
|
||||
@@ -2133,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
|
||||
|
||||
@@ -2145,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: {}
|
||||
|
||||
@@ -2190,7 +2190,7 @@ snapshots:
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
js-yaml@4.2.0:
|
||||
js-yaml@5.2.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
@@ -2224,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:
|
||||
@@ -2238,7 +2238,7 @@ snapshots:
|
||||
|
||||
make-dir@4.0.0:
|
||||
dependencies:
|
||||
semver: 7.8.3
|
||||
semver: 7.8.5
|
||||
|
||||
mime-db@1.54.0: {}
|
||||
|
||||
@@ -2266,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
|
||||
@@ -2280,9 +2280,9 @@ 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
|
||||
worker-timers: 8.0.33
|
||||
ws: 8.21.0
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -2337,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: {}
|
||||
@@ -2427,7 +2427,7 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
semver@7.8.3: {}
|
||||
semver@7.8.5: {}
|
||||
|
||||
send@1.2.1:
|
||||
dependencies:
|
||||
@@ -2472,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: {}
|
||||
@@ -2571,7 +2571,7 @@ snapshots:
|
||||
|
||||
typescript@6.0.3: {}
|
||||
|
||||
undici-types@7.18.2: {}
|
||||
undici-types@8.3.0: {}
|
||||
|
||||
unix-dgram@2.0.6:
|
||||
dependencies:
|
||||
@@ -2587,13 +2587,13 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vite-node@3.2.4(@types/node@24.13.1):
|
||||
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.13.1)
|
||||
vite: 6.3.5(@types/node@26.1.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -2608,7 +2608,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite@6.3.5(@types/node@24.13.1):
|
||||
vite@6.3.5(@types/node@26.1.1):
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
fdir: 6.4.6(picomatch@4.0.2)
|
||||
@@ -2617,14 +2617,14 @@ snapshots:
|
||||
rollup: 4.44.0
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 24.13.1
|
||||
'@types/node': 26.1.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@3.2.4(@types/node@24.13.1):
|
||||
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.13.1))
|
||||
'@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
|
||||
@@ -2642,11 +2642,11 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
tinypool: 1.1.1
|
||||
tinyrainbow: 2.0.0
|
||||
vite: 6.3.5(@types/node@24.13.1)
|
||||
vite-node: 3.2.4(@types/node@24.13.1)
|
||||
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.13.1
|
||||
'@types/node': 26.1.1
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- less
|
||||
@@ -2699,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:
|
||||
@@ -2740,21 +2740,21 @@ snapshots:
|
||||
|
||||
ws@8.21.0: {}
|
||||
|
||||
zigbee-herdsman-converters@26.63.0:
|
||||
zigbee-herdsman-converters@26.81.0:
|
||||
dependencies:
|
||||
iconv-lite: 0.7.2
|
||||
semver: 7.8.3
|
||||
zigbee-herdsman: 10.4.0
|
||||
iconv-lite: 0.7.3
|
||||
semver: 7.8.5
|
||||
zigbee-herdsman: 10.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
zigbee-herdsman@10.4.0:
|
||||
zigbee-herdsman@10.6.2:
|
||||
dependencies:
|
||||
'@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.4.0
|
||||
bonjour-service: 1.4.2
|
||||
debounce: 3.0.0
|
||||
fast-deep-equal: 3.1.3
|
||||
slip: 1.0.2
|
||||
@@ -2766,4 +2766,4 @@ snapshots:
|
||||
|
||||
zigbee2mqtt-frontend@0.9.21: {}
|
||||
|
||||
zigbee2mqtt-windfront@2.12.0: {}
|
||||
zigbee2mqtt-windfront@2.12.1: {}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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"});
|
||||
@@ -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");
|
||||
@@ -1252,6 +1423,30 @@ 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:
|
||||
@@ -1275,11 +1470,11 @@ 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,
|
||||
@@ -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;
|
||||
|
||||
@@ -1722,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({
|
||||
@@ -1762,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({
|
||||
@@ -3021,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();
|
||||
|
||||
@@ -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