fix: OTA availability detection (#30815)

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
Nerivec
2026-01-27 20:37:18 +01:00
committed by Koen Kanters
co-authored by Koen Kanters
parent 10478e797e
commit 476efaa6e1
3 changed files with 16 additions and 17 deletions
+5 -6
View File
@@ -218,7 +218,7 @@ export default class OTAUpdate extends Extension {
latest_release_notes: deviceUpdateState?.latest_release_notes,
}
: {
state: state.available === 0 ? "idle" : "available",
state: state.available ? "available" : "idle",
installed_version: state.current.fileVersion,
latest_version: state.availableMeta?.fileVersion ?? state.current.fileVersion,
latest_source: state.availableMeta?.url || null,
@@ -303,13 +303,12 @@ export default class OTAUpdate extends Extension {
await this.publishEntityState(device, this.#getEntityPublishPayload(device, availableResult));
this.#lastChecked.set(device.ieeeAddr, Date.now());
const available = availableResult.available !== 0;
const response = utils.getResponse<"bridge/response/device/ota_update/check">(message, {
id: ID,
update_available: available,
update_available: availableResult.available,
downgrade: source.downgrade,
source: availableResult.availableMeta?.url,
release_notes: availableResult.availableMeta?.releaseNotes,
downgrade: available ? availableResult.available === 1 : undefined,
});
await this.mqtt.publish("bridge/response/device/ota_update/check", stringify(response));
@@ -495,7 +494,7 @@ export default class OTAUpdate extends Extension {
if (to === undefined) {
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: 0, current: from}));
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: false, current: from}));
return [from.fileVersion, undefined];
}
@@ -503,7 +502,7 @@ export default class OTAUpdate extends Extension {
logger.info(`Finished update of '${device.name}'`);
this.#removeProgressAndRemainingFromState(device);
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: 0, current: to}));
await this.publishEntityState(device, this.#getEntityPublishPayload(device, {available: false, current: to}));
logger.info(() => `Device '${device.name}' was OTA updated from '${from.fileVersion}' to '${to.fileVersion}'`);
+1 -1
View File
@@ -614,9 +614,9 @@ export interface Zigbee2MQTTAPI {
"bridge/response/device/ota_update/check": {
id: string;
update_available: boolean;
downgrade?: boolean;
source?: string;
release_notes?: string;
downgrade?: boolean;
};
"bridge/request/device/ota_update/update": {
+10 -10
View File
@@ -585,7 +585,7 @@ describe("Extension: OTAUpdate", () => {
});
it("is able to check if OTA update is available", async () => {
devices.bulb.checkOta.mockResolvedValueOnce({available: 0, current: {...DEFAULT_CURRENT, fileVersion: 10}});
devices.bulb.checkOta.mockResolvedValueOnce({available: false, current: {...DEFAULT_CURRENT, fileVersion: 10}});
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/check", stringify({id: "bulb"}));
await flushPromises();
expect(devices.bulb.checkOta).toHaveBeenCalledTimes(1);
@@ -593,13 +593,13 @@ describe("Extension: OTAUpdate", () => {
expect(devices.bulb.updateOta).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/ota_update/check",
stringify({data: {id: "bulb", update_available: false}, status: "ok"}),
stringify({data: {id: "bulb", update_available: false, downgrade: false}, status: "ok"}),
{},
);
mockMQTTPublishAsync.mockClear();
devices.bulb.checkOta.mockResolvedValueOnce({
available: -1,
available: true,
current: {...DEFAULT_CURRENT, fileVersion: 10},
availableMeta: {...DEFAULT_AVAILABLE_META, fileVersion: 12},
});
@@ -613,7 +613,7 @@ describe("Extension: OTAUpdate", () => {
stringify({data: {id: "bulb", update_available: true, downgrade: false, source: "https://example.com/my.ota"}, status: "ok"}),
{},
);
devices.bulb.checkOta.mockResolvedValueOnce({available: 0, current: {...DEFAULT_CURRENT, fileVersion: 10}});
devices.bulb.checkOta.mockResolvedValueOnce({available: false, current: {...DEFAULT_CURRENT, fileVersion: 10}});
mockMQTTEvents.message("zigbee2mqtt/bridge/request/device/ota_update/check/downgrade", stringify({id: "bulb"}));
await flushPromises();
expect(devices.bulb.checkOta).toHaveBeenCalledTimes(3);
@@ -621,7 +621,7 @@ describe("Extension: OTAUpdate", () => {
expect(devices.bulb.updateOta).toHaveBeenCalledTimes(0);
expect(mockMQTTPublishAsync).toHaveBeenCalledWith(
"zigbee2mqtt/bridge/response/device/ota_update/check",
stringify({data: {id: "bulb", update_available: false}, status: "ok"}),
stringify({data: {id: "bulb", update_available: false, downgrade: true}, status: "ok"}),
{},
);
@@ -632,7 +632,7 @@ describe("Extension: OTAUpdate", () => {
mockMQTTPublishAsync.mockClear();
devices.bulb.checkOta.mockResolvedValueOnce({
available: 1,
available: true,
current: {...DEFAULT_CURRENT, fileVersion: 10},
availableMeta: {...DEFAULT_AVAILABLE_META, fileVersion: 8},
});
@@ -690,7 +690,7 @@ describe("Extension: OTAUpdate", () => {
it("allows check OTA with custom URL even when device does not support it", async () => {
devices.HGZB04D.checkOta.mockResolvedValueOnce({
available: -1,
available: true,
current: {...DEFAULT_CURRENT, fileVersion: 10},
availableMeta: {...DEFAULT_AVAILABLE_META, fileVersion: 14, releaseNotes: "New features"},
});
@@ -730,7 +730,7 @@ describe("Extension: OTAUpdate", () => {
setTimeout(
() =>
resolve({
available: 0,
available: false,
current: {...DEFAULT_CURRENT, fileVersion: 1},
}),
99999,
@@ -794,7 +794,7 @@ describe("Extension: OTAUpdate", () => {
it("checks for update when device requests it", async () => {
const data = {imageType: 12382, manufacturerCode: 2134, fileVersion: 33};
devices.bulb.checkOta.mockResolvedValueOnce({
available: -1,
available: true,
current: {...DEFAULT_CURRENT, ...data},
availableMeta: {...DEFAULT_AVAILABLE_META, fileVersion: 34},
});
@@ -864,7 +864,7 @@ describe("Extension: OTAUpdate", () => {
it("checks for update when device requests it and it is not available", async () => {
const data = {imageType: 12382, manufacturerCode: 2134, fileVersion: 33};
devices.bulb.checkOta.mockResolvedValueOnce({
available: 0,
available: false,
current: {...DEFAULT_CURRENT, ...data},
availableMeta: {...DEFAULT_AVAILABLE_META, fileVersion: 33},
});