From daaade45d919d982461e49cfa4cefd195e70cd7d Mon Sep 17 00:00:00 2001 From: koenkk Date: Sat, 7 Oct 2023 08:55:16 +0200 Subject: [PATCH] fix: Fix device keeps requesting OTA due to incorrect transaction sequence number. https://github.com/Koenkk/zigbee2mqtt/issues/19129 --- lib/extension/otaUpdate.ts | 3 ++- test/otaUpdate.test.js | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/extension/otaUpdate.ts b/lib/extension/otaUpdate.ts index 9a4b2e43..0a833642 100644 --- a/lib/extension/otaUpdate.ts +++ b/lib/extension/otaUpdate.ts @@ -119,7 +119,8 @@ export default class OTAUpdate extends Extension { // Respond to the OTA request: respond with NO_IMAGE_AVAILABLE (0x98) (so the client stops requesting OTAs) const endpoint = data.device.zh.endpoints.find((e) => e.supportsOutputCluster('genOta')) || data.endpoint; - await endpoint.commandResponse('genOta', 'queryNextImageResponse', {status: 0x98}); + await endpoint.commandResponse('genOta', 'queryNextImageResponse', + {status: 0x98}, undefined, data.meta.zclTransactionSequenceNumber); logger.debug(`Responded to OTA request of '${data.device.name}' with 'NO_IMAGE_AVAILABLE'`); } diff --git a/test/otaUpdate.test.js b/test/otaUpdate.test.js index b7184e51..1f671b17 100644 --- a/test/otaUpdate.test.js +++ b/test/otaUpdate.test.js @@ -252,7 +252,7 @@ describe('OTA update', () => { const mapped = zigbeeHerdsmanConverters.findByDevice(device) mockClear(mapped); mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 12}); - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; logger.info.mockClear(); await zigbeeHerdsman.events.message(payload); await flushPromises(); @@ -260,7 +260,7 @@ describe('OTA update', () => { expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, logger, {"imageType": 12382}); expect(logger.info).toHaveBeenCalledWith(`Update available for 'bulb'`); expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); - expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}); + expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}, undefined, 10); // Should not request again when device asks again after a short time await zigbeeHerdsman.events.message(payload); @@ -286,14 +286,14 @@ describe('OTA update', () => { const mapped = zigbeeHerdsmanConverters.findByDevice(device) mockClear(mapped); mapped.ota.isUpdateAvailable.mockImplementationOnce(() => {throw new Error('Nothing to find here')}) - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; logger.info.mockClear(); await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, logger, {"imageType": 12382}); expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); - expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}); + expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}, undefined, 10); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({"update_available":false,"update":{"state":"idle"}}), @@ -308,14 +308,14 @@ describe('OTA update', () => { const mapped = zigbeeHerdsmanConverters.findByDevice(device) mockClear(mapped); mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: false, currentFileVersion: 13, otaFileVersion: 13}); - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; logger.info.mockClear(); await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledTimes(1); expect(mapped.ota.isUpdateAvailable).toHaveBeenCalledWith(device, logger, {"imageType": 12382}); expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); - expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}); + expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 0x98}, undefined, 10); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', stringify({"update_available":false,"update":{"state":"idle","installed_version": 13, "latest_version": 13}}), @@ -330,7 +330,7 @@ describe('OTA update', () => { const mapped = zigbeeHerdsmanConverters.findByDevice(device) mockClear(mapped); mapped.ota.isUpdateAvailable.mockReturnValueOnce({available: true, currentFileVersion: 10, otaFileVersion: 13}); - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; logger.info.mockClear(); await zigbeeHerdsman.events.message(payload); await flushPromises(); @@ -340,22 +340,22 @@ describe('OTA update', () => { it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA', async () => { const device = zigbeeHerdsman.devices.HGZB04D; const data = {imageType: 12382}; - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); - expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 152}); + expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 152}, undefined, 10); }); it('Should respond with NO_IMAGE_AVAILABLE when not supporting OTA and device has no OTA endpoint to standard endpoint', async () => { const device = zigbeeHerdsman.devices.SV01; const data = {imageType: 12382}; - const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10}; + const payload = {data, cluster: 'genOta', device, endpoint: device.getEndpoint(1), type: 'commandQueryNextImageRequest', linkquality: 10, meta: {zclTransactionSequenceNumber: 10}}; logger.error.mockClear(); await zigbeeHerdsman.events.message(payload); await flushPromises(); expect(device.endpoints[0].commandResponse).toHaveBeenCalledTimes(1); - expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 152}); + expect(device.endpoints[0].commandResponse).toHaveBeenCalledWith("genOta", "queryNextImageResponse", {"status": 152}, undefined, 10); }); it('Legacy api: Should OTA update a device', async () => {