From d73c42af71c4e1288e712cf8b427136403e616fe Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Tue, 31 Mar 2020 21:31:18 +0200 Subject: [PATCH] Throw error when read, write or configureReporting status is not successful. https://github.com/Koenkk/zigbee2mqtt/issues/3256 --- src/controller/model/endpoint.ts | 17 +++++++++-- src/zcl/definition/status.ts | 13 +++++---- src/zcl/zclFrame.ts | 4 ++- test/controller.test.ts | 48 ++++++++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/controller/model/endpoint.ts b/src/controller/model/endpoint.ts index ede60d8afa..0c6ed9e837 100644 --- a/src/controller/model/endpoint.ts +++ b/src/controller/model/endpoint.ts @@ -172,6 +172,14 @@ class Endpoint extends Entity { * Zigbee functions */ + private checkStatus(payload: [{status: Zcl.Status}]): void { + for (const item of payload) { + if (item.status !== Zcl.Status.SUCCESS) { + throw new Error(`Status '${Zcl.Status[item.status]}'`); + } + } + } + public async write( clusterKey: number | string, attributes: KeyValue, options?: Options ): Promise { @@ -198,9 +206,10 @@ class Endpoint extends Entity { Zcl.FrameType.GLOBAL, Zcl.Direction.CLIENT_TO_SERVER, options.disableDefaultResponse, options.manufacturerCode, ZclTransactionSequenceNumber.next(), 'write', cluster.ID, payload ); - await Entity.adapter.sendZclFrameToEndpoint( + const result = await Entity.adapter.sendZclFrameToEndpoint( this.deviceNetworkAddress, this.ID, frame, options.timeout, ); + this.checkStatus(result.frame.Payload); } catch (error) { const message = `${log} failed (${error})`; debug.error(message); @@ -231,6 +240,7 @@ class Endpoint extends Entity { const result = await Entity.adapter.sendZclFrameToEndpoint( this.deviceNetworkAddress, this.ID, frame, options.timeout, ); + this.checkStatus(result.frame.Payload); return ZclFrameConverter.attributeKeyValue(result.frame); } catch (error) { const message = `${log} failed (${error})`; @@ -404,7 +414,10 @@ class Endpoint extends Entity { debug.info(log); try { - await Entity.adapter.sendZclFrameToEndpoint(this.deviceNetworkAddress, this.ID, frame, options.timeout); + const result = await Entity.adapter.sendZclFrameToEndpoint( + this.deviceNetworkAddress, this.ID, frame, options.timeout + ); + this.checkStatus(result.frame.Payload); } catch (error) { const message = `${log} failed (${error})`; debug.error(message); diff --git a/src/zcl/definition/status.ts b/src/zcl/definition/status.ts index 0497bdbd68..5f678bffb6 100644 --- a/src/zcl/definition/status.ts +++ b/src/zcl/definition/status.ts @@ -2,13 +2,14 @@ enum Status { SUCCESS = 0, FAILURE = 1, NOT_AUTHORIZED = 126, - MALFORMED_CMD = 128, - UNSUP_CLUSTER_CMD = 129, - UNSUP_GENERAL_CMD = 130, - UNSUP_MANU_CLUSTER_CMD = 131, - UNSUP_MANU_GENERAL_CMD = 132, + RESERVED_FIELD_NOT_ZERO = 127, + MALFORMED_COMMAND = 128, + UNSUP_CLUSTER_COMMAND = 129, + UNSUP_GENERAL_COMMAND = 130, + UNSUP_MANUF_CLUSTER_COMMAND = 131, + UNSUP_MANUF_GENERAL_COMMAND = 132, INVALID_FIELD = 133, - UNSUP_ATTRIBUTE = 134, + UNSUPPORTED_ATTRIBUTE = 134, INVALID_VALUE = 135, READ_ONLY = 136, INSUFFICIENT_SPACE = 137, diff --git a/src/zcl/zclFrame.ts b/src/zcl/zclFrame.ts index b7bb0c298e..79f93c5b33 100644 --- a/src/zcl/zclFrame.ts +++ b/src/zcl/zclFrame.ts @@ -359,7 +359,9 @@ class ZclFrame { } // List of commands is not completed, feel free to add more. - public isCommand(commandName: 'read' | 'report' | 'readRsp' | 'remove' | 'add' | 'write' | 'enrollReq'): boolean { + public isCommand( + commandName: 'read' | 'report' | 'readRsp' | 'remove' | 'add' | 'write' | 'enrollReq' | 'configReport' + ): boolean { return this.getCommand().name === commandName; } diff --git a/test/controller.test.ts b/test/controller.test.ts index 6d5b637e18..ef3e331e3a 100755 --- a/test/controller.test.ts +++ b/test/controller.test.ts @@ -58,6 +58,7 @@ const mocksendZclFrameToEndpoint = jest.fn(); let iasZoneReadState170Count = 0; let enroll170 = true; +let configureReportStatus = 0; const restoreMocksendZclFrameToEndpoint = () => { mocksendZclFrameToEndpoint.mockImplementation((networkAddress, endpoint, frame: ZclFrame) => { @@ -67,10 +68,10 @@ const restoreMocksendZclFrameToEndpoint = () => { for (const item of frame.Payload) { if (frame.isCluster('ssIasZone') && item.attrId === 0) { iasZoneReadState170Count++; - payload.push({attrId: item.attrId, attrData: iasZoneReadState170Count === 2 && enroll170 ? 1 : 0}); + payload.push({attrId: item.attrId, attrData: iasZoneReadState170Count === 2 && enroll170 ? 1 : 0, status: 0}); } else if (item.attrId !== 65314) { const attribute = cluster.getAttribute(item.attrId).name; - payload.push({attrId: item.attrId, attrData: mockDevices[networkAddress].attributes[endpoint][attribute]}) + payload.push({attrId: item.attrId, attrData: mockDevices[networkAddress].attributes[endpoint][attribute], status: 0}) } } @@ -99,6 +100,26 @@ const restoreMocksendZclFrameToEndpoint = () => { groupID: 1, }); } + + if (frame.isGlobal() && frame.isCommand('write')) { + const payload = []; + for (const item of frame.Payload) { + payload.push({attrId: item.attrId, status: 0}) + } + + // @ts-ignore + return {frame: new ZclFrame(null, payload, frame.Cluster)}; + } + + if (frame.isGlobal() && frame.isCommand('configReport')) { + const payload = []; + for (const item of frame.Payload) { + payload.push({attrId: item.attrId, status: configureReportStatus, direction: 1}) + } + + // @ts-ignore + return {frame: new ZclFrame(null, payload, frame.Cluster)}; + } }) } @@ -368,6 +389,7 @@ describe('Controller', () => { // @ts-ignore zclTransactionSequenceNumber.number = 1; iasZoneReadState170Count = 0; + configureReportStatus = 0; skipWait = false; enroll170 = true; options.network.channelList = [15]; @@ -1960,6 +1982,28 @@ describe('Controller', () => { }); }); + it('Endpoint configure reporting fails when status code is not 0', async () => { + configureReportStatus = 1; + await controller.start(); + await mockAdapterEvents['deviceJoined']({networkAddress: 129, ieeeAddr: '0x129'}); + const device = controller.getDeviceByIeeeAddr('0x129'); + const endpoint = device.getEndpoint(1); + mocksendZclFrameToEndpoint.mockClear(); + let error; + try { + await endpoint.configureReporting('genPowerCfg', [{ + attribute: 'mainsFrequency', + minimumReportInterval: 1, + maximumReportInterval: 10, + reportableChange: 1, + }]); + } + catch (e) { + error = e; + } + expect(error).toStrictEqual(new Error(`ConfigureReporting 0x129/1 genPowerCfg([{"attribute":"mainsFrequency","minimumReportInterval":1,"maximumReportInterval":10,"reportableChange":1}], {"timeout":6000,"manufacturerCode":null,"disableDefaultResponse":true}) failed (Error: Status 'FAILURE')`)); + }); + it('Return group from databse when not in lookup', async () => { await controller.start(); await controller.createGroup(2);