Compare commits

...
12 Commits
Author SHA1 Message Date
Koen Kanters fd8bafc81d chore(dev): release 2.12.0 (#32157) 2026-06-09 21:02:26 +02:00
Koen Kanters 68daf4f1de fix(ignore): update zigbee-herdsman-converters to 26.63.0 (#32252) 2026-06-09 18:57:20 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 41f6886591 fix(ignore): bump the minor-patch group across 1 directory with 3 updates (#32236)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-09 20:24:54 +02:00
Koen Kanters 582f153896 fix(ignore): update zigbee-herdsman to 10.4.0 (#32249) 2026-06-09 18:19:56 +00:00
Nerivec 6c2c874641 fix(ignore): missing type for new OTA endpoint (#32195) 2026-06-03 19:24:16 +00:00
Koen Kanters 120f6ac779 fix(ignore): update zigbee-herdsman-converters to 26.62.0 (#32194) 2026-06-03 19:05:29 +00:00
de58711e11 feat: Add ability to abort running OTA (#32022)
Co-authored-by: Andy <andrei_lazarov@yahoo.com>
Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
2026-06-03 21:01:44 +02:00
Koen Kanters ea6ccac938 fix(ignore): update zigbee-herdsman to 10.3.0 (#32193) 2026-06-03 18:57:34 +00:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> ef8dcb4dc2 fix(ignore): bump the minor-patch group with 2 updates (#32183)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-03 20:42:49 +02:00
Koen Kanters babf7a17d4 fix(ignore): update zigbee-herdsman-converters to 26.61.2 (#32180) 2026-06-02 18:31:41 +00:00
Koen Kanters f9e1bac588 fix(ignore): update zigbee-herdsman to 10.2.0 (#32179) 2026-06-02 18:25:44 +00:00
github-actions[bot] 6aac849452 chore: promote to dev 2026-06-01 18:52:00 +00:00
8 changed files with 199 additions and 85 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{
".": "2.11.0"
".": "2.12.0"
}
+20
View File
@@ -1,5 +1,25 @@
# Changelog
## [2.12.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.11.0...2.12.0) (2026-06-09)
### Features
* Add ability to abort running OTA ([#32022](https://github.com/Koenkk/zigbee2mqtt/issues/32022)) ([de58711](https://github.com/Koenkk/zigbee2mqtt/commit/de58711e11a5ca07c5b55a0c17a1d9f3f452c110))
### Bug Fixes
* **ignore:** bump the minor-patch group across 1 directory with 3 updates ([#32236](https://github.com/Koenkk/zigbee2mqtt/issues/32236)) ([41f6886](https://github.com/Koenkk/zigbee2mqtt/commit/41f688659119012a9a84063bd67c2769a1b1892c))
* **ignore:** bump the minor-patch group with 2 updates ([#32183](https://github.com/Koenkk/zigbee2mqtt/issues/32183)) ([ef8dcb4](https://github.com/Koenkk/zigbee2mqtt/commit/ef8dcb4dc2800defd97fda7523297eab8d2566dc))
* **ignore:** missing type for new OTA endpoint ([#32195](https://github.com/Koenkk/zigbee2mqtt/issues/32195)) ([6c2c874](https://github.com/Koenkk/zigbee2mqtt/commit/6c2c874641ada9cae1c9bdd963a3201cb1cf0fef))
* **ignore:** update zigbee-herdsman to 10.2.0 ([#32179](https://github.com/Koenkk/zigbee2mqtt/issues/32179)) ([f9e1bac](https://github.com/Koenkk/zigbee2mqtt/commit/f9e1bac588e65b223e75f4a2b2e9d528933fdbd8))
* **ignore:** update zigbee-herdsman to 10.3.0 ([#32193](https://github.com/Koenkk/zigbee2mqtt/issues/32193)) ([ea6ccac](https://github.com/Koenkk/zigbee2mqtt/commit/ea6ccac9380af1d9f5672174b2545409f36c2769))
* **ignore:** update zigbee-herdsman to 10.4.0 ([#32249](https://github.com/Koenkk/zigbee2mqtt/issues/32249)) ([582f153](https://github.com/Koenkk/zigbee2mqtt/commit/582f1538961af9c1b6434e6a1bc4acebc63a2541))
* **ignore:** update zigbee-herdsman-converters to 26.61.2 ([#32180](https://github.com/Koenkk/zigbee2mqtt/issues/32180)) ([babf7a1](https://github.com/Koenkk/zigbee2mqtt/commit/babf7a17d4617217297eef45f6b8307265f9b921))
* **ignore:** update zigbee-herdsman-converters to 26.62.0 ([#32194](https://github.com/Koenkk/zigbee2mqtt/issues/32194)) ([120f6ac](https://github.com/Koenkk/zigbee2mqtt/commit/120f6ac779c9fda6914fc68c56674e589c0e82ba))
* **ignore:** update zigbee-herdsman-converters to 26.63.0 ([#32252](https://github.com/Koenkk/zigbee2mqtt/issues/32252)) ([68daf4f](https://github.com/Koenkk/zigbee2mqtt/commit/68daf4f1deafad3ee6e542acbd77d403340e2caa))
## [2.11.0](https://github.com/Koenkk/zigbee2mqtt/compare/2.10.1...2.11.0) (2026-06-01)
+29 -12
View File
@@ -50,7 +50,7 @@ function writeFirmwareHexToDataDir(hex: string, fileName: string | undefined, de
export default class OTAUpdate extends Extension {
#topicRegex = new RegExp(
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade)?`,
`^${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/(update|check|schedule|unschedule)/?(downgrade|abort)?`,
"i",
);
#inProgress = new Set<string>();
@@ -248,6 +248,7 @@ export default class OTAUpdate extends Extension {
| Zigbee2MQTTAPI["bridge/request/device/ota_update/check/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/update/abort"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/schedule/downgrade"]
| Zigbee2MQTTAPI["bridge/request/device/ota_update/unschedule"];
@@ -258,18 +259,31 @@ export default class OTAUpdate extends Extension {
assert(message.id, "Invalid payload");
}
const ID = (messageObject ? message.id : message) as string;
const device = this.zigbee.resolveEntity(ID);
const id = (messageObject ? message.id : message) as string;
const device = this.zigbee.resolveEntity(id);
const type = topicMatch[1] as "check" | "update" | "schedule" | "unschedule";
const downgrade = topicMatch[2] === "downgrade";
const abort = topicMatch[2] === "abort";
let error: string | undefined;
let errorStack: string | undefined;
if (!(device instanceof Device)) {
error = `Device '${ID}' does not exist`;
error = `Device '${id}' does not exist`;
} else if (this.#inProgress.has(device.ieeeAddr)) {
// also guards against scheduling while check/update op in progress that could result in undesired OTA state
error = `OTA update or check for update already in progress for '${device.name}'`;
if (abort) {
device.zh.abortOta();
this.#inProgress.delete(device.ieeeAddr);
// cleanup same as a fail
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "available"));
await this.mqtt.publish(
"bridge/response/device/ota_update/update/abort",
stringify(utils.getResponse<"bridge/response/device/ota_update/update/abort">(message, {id})),
);
} else {
// also guards against scheduling while check/update op in progress that could result in undesired OTA state
error = `OTA update or check for update already in progress for '${device.name}'`;
}
} else {
switch (type) {
case "check": {
@@ -304,7 +318,7 @@ export default class OTAUpdate extends Extension {
this.#lastChecked.set(device.ieeeAddr, Date.now());
const response = utils.getResponse<"bridge/response/device/ota_update/check">(message, {
id: ID,
id,
update_available: availableResult.available,
downgrade: source.downgrade,
source: availableResult.availableMeta?.url,
@@ -321,6 +335,11 @@ export default class OTAUpdate extends Extension {
}
case "update": {
if (abort) {
error = `No OTA in progress to abort for device '${device.name}'`;
break;
}
this.#inProgress.add(device.ieeeAddr);
const otaSettings = settings.get().ota;
@@ -378,7 +397,7 @@ export default class OTAUpdate extends Extension {
const firmwareTo = await this.#readSoftwareBuildIDAndDateCode(device);
const response = utils.getResponse<"bridge/response/device/ota_update/update">(message, {
id: ID,
id,
from: {
file_version: fromVersion,
software_build_id: firmwareFrom?.softwareBuildID,
@@ -427,7 +446,7 @@ export default class OTAUpdate extends Extension {
device.zh.scheduleOta(source);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "scheduled"));
const response = utils.getResponse<"bridge/response/device/ota_update/schedule">(message, {id: ID, url: source.url});
const response = utils.getResponse<"bridge/response/device/ota_update/schedule">(message, {id, url: source.url});
await this.mqtt.publish("bridge/response/device/ota_update/schedule", stringify(response));
@@ -442,9 +461,7 @@ export default class OTAUpdate extends Extension {
device.zh.unscheduleOta();
await this.publishEntityState(device, this.#getEntityPublishPayload(device, "idle"));
const response = utils.getResponse<"bridge/response/device/ota_update/unschedule">(message, {
id: ID,
});
const response = utils.getResponse<"bridge/response/device/ota_update/unschedule">(message, {id});
await this.mqtt.publish("bridge/response/device/ota_update/unschedule", stringify(response));
+10
View File
@@ -677,6 +677,14 @@ export interface Zigbee2MQTTAPI {
default_maximum_data_size?: number | null;
};
"bridge/request/device/ota_update/update/abort": {
id: string;
};
"bridge/response/device/ota_update/update/abort": {
id: string;
};
"bridge/response/device/ota_update/update": {
id: string;
from:
@@ -1001,6 +1009,7 @@ export type Zigbee2MQTTRequestEndpoints =
| "bridge/request/device/ota_update/check/downgrade"
| "bridge/request/device/ota_update/update"
| "bridge/request/device/ota_update/update/downgrade"
| "bridge/request/device/ota_update/update/abort"
| "bridge/request/device/ota_update/schedule"
| "bridge/request/device/ota_update/schedule/downgrade"
| "bridge/request/device/ota_update/unschedule"
@@ -1050,6 +1059,7 @@ export type Zigbee2MQTTResponseEndpoints =
| "bridge/response/device/remove"
| "bridge/response/device/ota_update/check"
| "bridge/response/device/ota_update/update"
| "bridge/response/device/ota_update/update/abort"
| "bridge/response/device/ota_update/schedule"
| "bridge/response/device/ota_update/unschedule"
| "bridge/response/device/interview"
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "zigbee2mqtt",
"version": "2.11.0",
"version": "2.12.0",
"description": "Zigbee to MQTT bridge using Zigbee-herdsman",
"main": "index.js",
"types": "dist/types/api.d.ts",
@@ -49,30 +49,30 @@
"fast-deep-equal": "^3.1.3",
"finalhandler": "^2.1.1",
"humanize-duration": "^3.33.2",
"js-yaml": "^4.1.0",
"js-yaml": "^4.2.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"jszip": "^3.10.1",
"mqtt": "^5.15.1",
"object-assign-deep": "^0.4.0",
"rimraf": "^6.1.3",
"semver": "^7.8.1",
"semver": "^7.8.3",
"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.1.0",
"zigbee-herdsman-converters": "26.61.1",
"zigbee-herdsman": "10.4.0",
"zigbee-herdsman-converters": "26.63.0",
"zigbee2mqtt-frontend": "0.9.21",
"zigbee2mqtt-windfront": "2.11.3"
"zigbee2mqtt-windfront": "2.12.0"
},
"devDependencies": {
"@biomejs/biome": "^2.4.16",
"@types/finalhandler": "^1.2.3",
"@types/humanize-duration": "^3.27.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.12.4",
"@types/node": "^24.13.1",
"@types/object-assign-deep": "^0.4.3",
"@types/readable-stream": "4.0.23",
"@types/serve-static": "^2.2.0",
+65 -65
View File
@@ -5,7 +5,7 @@ settings:
excludeLinksFromLockfile: false
overrides:
zigbee-herdsman: 10.1.0
zigbee-herdsman: 10.4.0
importers:
@@ -33,8 +33,8 @@ importers:
specifier: ^3.33.2
version: 3.33.2
js-yaml:
specifier: ^4.1.0
version: 4.1.1
specifier: ^4.2.0
version: 4.2.0
json-stable-stringify-without-jsonify:
specifier: ^1.0.1
version: 1.0.1
@@ -51,8 +51,8 @@ importers:
specifier: ^6.1.3
version: 6.1.3
semver:
specifier: ^7.8.1
version: 7.8.1
specifier: ^7.8.3
version: 7.8.3
source-map-support:
specifier: ^0.5.21
version: 0.5.21
@@ -72,17 +72,17 @@ importers:
specifier: ^8.21.0
version: 8.21.0
zigbee-herdsman:
specifier: 10.1.0
version: 10.1.0
specifier: 10.4.0
version: 10.4.0
zigbee-herdsman-converters:
specifier: 26.61.1
version: 26.61.1
specifier: 26.63.0
version: 26.63.0
zigbee2mqtt-frontend:
specifier: 0.9.21
version: 0.9.21
zigbee2mqtt-windfront:
specifier: 2.11.3
version: 2.11.3
specifier: 2.12.0
version: 2.12.0
devDependencies:
'@biomejs/biome':
specifier: ^2.4.16
@@ -97,8 +97,8 @@ importers:
specifier: ^4.0.9
version: 4.0.9
'@types/node':
specifier: ^24.12.4
version: 24.12.4
specifier: ^24.13.1
version: 24.13.1
'@types/object-assign-deep':
specifier: ^0.4.3
version: 0.4.3
@@ -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.12.4))
version: 3.2.4(vitest@3.2.4(@types/node@24.13.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.12.4)
version: 3.2.4(@types/node@24.13.1)
optionalDependencies:
unix-dgram:
specifier: ^2.0.7
@@ -219,8 +219,8 @@ packages:
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@date-fns/tz@1.4.1':
resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==}
'@date-fns/tz@1.5.0':
resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
'@esbuild/aix-ppc64@0.25.5':
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
@@ -553,8 +553,8 @@ packages:
'@types/js-yaml@4.0.9':
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
'@types/node@24.12.4':
resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
'@types/node@24.13.1':
resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==}
'@types/object-assign-deep@0.4.3':
resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==}
@@ -664,8 +664,8 @@ packages:
bl@6.1.6:
resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==}
bonjour-service@1.3.0:
resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==}
bonjour-service@1.4.0:
resolution: {integrity: sha512-fGQtj1qdR9vIKjFiWPQd52qIqwjaYqhcI40JEiDuvlZ86E7ZBPBwY9fPgHy9r2rYGIjiRfctNPYz6OQU73ww2w==}
brace-expansion@2.1.0:
resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==}
@@ -952,8 +952,8 @@ packages:
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
js-yaml@4.2.0:
resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==}
hasBin: true
json-schema-traverse@1.0.0:
@@ -1155,8 +1155,8 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
semver@7.8.1:
resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
semver@7.8.3:
resolution: {integrity: sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==}
engines: {node: '>=10'}
hasBin: true
@@ -1316,8 +1316,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
undici-types@7.18.2:
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
unix-dgram@2.0.6:
resolution: {integrity: sha512-AURroAsb73BZ6CdAyMrTk/hYKNj3DuYYEuOaB8bYMOHGKupRNScw90Q5C71tWJc3uE7dIeXRyuwN0xLLq3vDTg==}
@@ -1459,12 +1459,12 @@ packages:
utf-8-validate:
optional: true
zigbee-herdsman-converters@26.61.1:
resolution: {integrity: sha512-cCt42o58p0KpdzeUb5zLkldnLI9nIOcOdydkV+XyI7VH95LTjCbW89ax71XV3uRV1rnLHZLeEVXsFzf2U7w8aA==}
zigbee-herdsman-converters@26.63.0:
resolution: {integrity: sha512-5Hg4CNeu2PW0IZ+GsFtf8rqdMA8WrIMx+XZtpL4QWAOx5WpuzbyuFPseZKbs4iswtuf/zzPFZrGt07sx83srRg==}
engines: {node: '>=20.15.0'}
zigbee-herdsman@10.1.0:
resolution: {integrity: sha512-qFl/QsHgnYXn3LhqqsP3ZgmdQtHxiZ5JRwUpy1KaGnmR33In99f0ZNsoekxfuXFHmvxvQkqoCPFEqV1iukS+lg==}
zigbee-herdsman@10.4.0:
resolution: {integrity: sha512-LO0LV9Qibgv4GuEoDjwZa1No18owetv0iQ1vZZ06rbv+SOUsg8uILBTCv5+S7PKYUYF4krVJH32x2qXYu02r5Q==}
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.11.3:
resolution: {integrity: sha512-CANPCKiJMsVD6NEbtJprDGvatQ62txBWPV1sCcdR5cvR7rXuGsHlJjj1N/YJGbsQ0N+eN8+CyBXzNLj1YWcRdQ==}
zigbee2mqtt-windfront@2.12.0:
resolution: {integrity: sha512-sDwgEFlmKMj9Sl9FLhfZPkYEd7185v4spwxYXZl7gwQR3xrxeWmnLHtpQ3DfTJd2Ns6V6hzC80Sp8EmB2z8KKg==}
engines: {node: '>=22.12.0'}
snapshots:
@@ -1545,7 +1545,7 @@ snapshots:
enabled: 2.0.0
kuler: 2.0.0
'@date-fns/tz@1.4.1': {}
'@date-fns/tz@1.5.0': {}
'@esbuild/aix-ppc64@0.25.5':
optional: true
@@ -1757,7 +1757,7 @@ snapshots:
'@types/finalhandler@1.2.4':
dependencies:
'@types/node': 24.12.4
'@types/node': 24.13.1
'@types/http-errors@2.0.5': {}
@@ -1765,28 +1765,28 @@ snapshots:
'@types/js-yaml@4.0.9': {}
'@types/node@24.12.4':
'@types/node@24.13.1':
dependencies:
undici-types: 7.16.0
undici-types: 7.18.2
'@types/object-assign-deep@0.4.3': {}
'@types/readable-stream@4.0.23':
dependencies:
'@types/node': 24.12.4
'@types/node': 24.13.1
'@types/serve-static@2.2.0':
dependencies:
'@types/http-errors': 2.0.5
'@types/node': 24.12.4
'@types/node': 24.13.1
'@types/triple-beam@1.3.5': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 24.12.4
'@types/node': 24.13.1
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.12.4))':
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.13.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.12.4)
vitest: 3.2.4(@types/node@24.13.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.12.4))':
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.13.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
vite: 6.3.5(@types/node@24.12.4)
vite: 6.3.5(@types/node@24.13.1)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -1902,7 +1902,7 @@ snapshots:
inherits: 2.0.4
readable-stream: 4.7.0
bonjour-service@1.3.0:
bonjour-service@1.4.0:
dependencies:
fast-deep-equal: 3.1.3
multicast-dns: 7.2.5
@@ -2190,7 +2190,7 @@ snapshots:
js-tokens@9.0.1: {}
js-yaml@4.1.1:
js-yaml@4.2.0:
dependencies:
argparse: 2.0.1
@@ -2238,7 +2238,7 @@ snapshots:
make-dir@4.0.0:
dependencies:
semver: 7.8.1
semver: 7.8.3
mime-db@1.54.0: {}
@@ -2427,7 +2427,7 @@ snapshots:
safer-buffer@2.1.2: {}
semver@7.8.1: {}
semver@7.8.3: {}
send@1.2.1:
dependencies:
@@ -2571,7 +2571,7 @@ snapshots:
typescript@6.0.3: {}
undici-types@7.16.0: {}
undici-types@7.18.2: {}
unix-dgram@2.0.6:
dependencies:
@@ -2587,13 +2587,13 @@ snapshots:
util-deprecate@1.0.2: {}
vite-node@3.2.4(@types/node@24.12.4):
vite-node@3.2.4(@types/node@24.13.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 6.3.5(@types/node@24.12.4)
vite: 6.3.5(@types/node@24.13.1)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -2608,7 +2608,7 @@ snapshots:
- tsx
- yaml
vite@6.3.5(@types/node@24.12.4):
vite@6.3.5(@types/node@24.13.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.12.4
'@types/node': 24.13.1
fsevents: 2.3.3
vitest@3.2.4(@types/node@24.12.4):
vitest@3.2.4(@types/node@24.13.1):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.12.4))
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.13.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.12.4)
vite-node: 3.2.4(@types/node@24.12.4)
vite: 6.3.5(@types/node@24.13.1)
vite-node: 3.2.4(@types/node@24.13.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 24.12.4
'@types/node': 24.13.1
transitivePeerDependencies:
- jiti
- less
@@ -2740,21 +2740,21 @@ snapshots:
ws@8.21.0: {}
zigbee-herdsman-converters@26.61.1:
zigbee-herdsman-converters@26.63.0:
dependencies:
iconv-lite: 0.7.2
semver: 7.8.1
zigbee-herdsman: 10.1.0
semver: 7.8.3
zigbee-herdsman: 10.4.0
transitivePeerDependencies:
- supports-color
zigbee-herdsman@10.1.0:
zigbee-herdsman@10.4.0:
dependencies:
'@date-fns/tz': 1.4.1
'@date-fns/tz': 1.5.0
'@serialport/bindings-cpp': 13.0.1
'@serialport/parser-delimiter': 13.0.0
'@serialport/stream': 13.0.0
bonjour-service: 1.3.0
bonjour-service: 1.4.0
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.11.3: {}
zigbee2mqtt-windfront@2.12.0: {}
+65
View File
@@ -546,6 +546,71 @@ describe("Extension: OTAUpdate", () => {
expect(existsSync(saveFilePath)).toStrictEqual(false);
});
it("aborts running OTA", async () => {
let timer: NodeJS.Timeout | undefined;
devices.bulb.updateOta.mockImplementationOnce(
async (_source, _requestPayload, _requestTsn, _extraMetas, onProgress, _dataSettings, _endpoint) => {
onProgress(0, 36000.5678);
onProgress(10, 3600.2123);
return await new Promise((resolve) => {
timer = setTimeout(
() =>
resolve([
{
...DEFAULT_CURRENT,
fileVersion: 1,
},
{
...DEFAULT_CURRENT,
fileVersion: 90,
},
]),
10000,
);
});
},
);
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update", stringify({id: "bulb"}));
await flushPromises();
await vi.advanceTimersByTimeAsync(9000);
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update/abort", stringify({id: "bulb"}));
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bulb",
stringify({update: {state: "updating", progress: 0, remaining: 36001}}),
{
retain: true,
qos: 0,
},
);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bulb",
stringify({update: {state: "updating", progress: 10, remaining: 3600}}),
{retain: true, qos: 0},
);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith("zigbee2mqtt/bulb", stringify({update: {state: "available"}}), {retain: true, qos: 0});
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/ota_update/update/abort",
stringify({data: {id: "bulb"}, status: "ok"}),
{},
);
clearTimeout(timer);
});
it("handles abort when no running OTA", async () => {
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/update/abort", stringify({id: "bulb"}));
await flushPromises();
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/ota_update/update",
stringify({data: {}, status: "error", error: `No OTA in progress to abort for device 'bulb'`}),
{},
);
});
it("handles when OTA update fails", async () => {
devices.bulb.endpoints[0].read.mockImplementation(() => {
return {swBuildId: 1, dateCode: "2019010"};
+2
View File
@@ -283,6 +283,7 @@ export class Device {
unscheduleOta = vi.fn(() => {
this.scheduledOta = undefined;
});
abortOta = vi.fn();
scheduledOta: OtaSource | undefined = undefined;
constructor(
@@ -345,6 +346,7 @@ export class Device {
this.updateOta.mockClear();
this.scheduleOta.mockClear();
this.unscheduleOta.mockClear();
this.abortOta.mockClear();
this.meta = {};
this.scheduledOta = undefined;