From f95eb4527a22ae4e21fff5e185dc22ba875c81af Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 1 Apr 2020 20:33:04 +0200 Subject: [PATCH] Only setup supported color capabilities. https://github.com/Koenkk/zigbee2mqtt/issues/3260 --- lib/extension/deviceReport.js | 38 ++++++++++++++++++++++++++++---- test/deviceReport.test.js | 41 ++++++++++++++++++++++++++++++----- test/stub/zigbeeHerdsman.js | 6 ++--- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/lib/extension/deviceReport.js b/lib/extension/deviceReport.js index af18002e..cc24e898 100644 --- a/lib/extension/deviceReport.js +++ b/lib/extension/deviceReport.js @@ -16,6 +16,18 @@ const devicesNotSupportingReporting = [CC2530Router, BASICZBR3]; const reportKey = 1; +const getColorCapabilities = async (endpoint) => { + if (endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities') === undefined) { + await endpoint.read('lightingColorCtrl', ['colorCapabilities']); + } + + const value = endpoint.getClusterAttributeValue('lightingColorCtrl', 'colorCapabilities'); + return { + colorTemperature: (value & 1<<4) > 0, + colorXY: (value & 1<<3) > 0, + }; +}; + const clusters = { 'genOnOff': [ {attribute: 'onOff', ...defaultConfiguration, minimumReportInterval: 0, reportableChange: 0}, @@ -24,9 +36,18 @@ const clusters = { {attribute: 'currentLevel', ...defaultConfiguration}, ], 'lightingColorCtrl': [ - {attribute: 'colorTemperature', ...defaultConfiguration}, - {attribute: 'currentX', ...defaultConfiguration}, - {attribute: 'currentY', ...defaultConfiguration}, + { + attribute: 'colorTemperature', ...defaultConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorTemperature, + }, + { + attribute: 'currentX', ...defaultConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY, + }, + { + attribute: 'currentY', ...defaultConfiguration, + condition: async (endpoint) => (await getColorCapabilities(endpoint)).colorXY, + }, ], 'closuresWindowCovering': [ {attribute: 'currentPositionLiftPercentage', ...defaultConfiguration}, @@ -103,8 +124,17 @@ class DeviceReport extends BaseExtension { for (const [cluster, configuration] of Object.entries(clusters)) { if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, mappedDevice)) { logger.debug(`Setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`); + + const items = []; + for (const entry of configuration) { + if (!entry.hasOwnProperty('condition') || (await entry.condition(ep))) { + items.push({...entry}); + delete items[items.length - 1].condition; + } + } + await ep.bind(cluster, this.coordinatorEndpoint); - await ep.configureReporting(cluster, configuration); + await ep.configureReporting(cluster, items); logger.info( `Successfully setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`, ); diff --git a/test/deviceReport.test.js b/test/deviceReport.test.js index d0322c9c..b4190ac9 100644 --- a/test/deviceReport.test.js +++ b/test/deviceReport.test.js @@ -22,7 +22,7 @@ const mocksClear = [MQTT.publish, logger.warn, logger.debug, debounce]; describe('Device report', () => { let controller; - function expectOnOffBrightnessColorReport(endpoint) { + function expectOnOffBrightnessColorReport(endpoint, colorXY) { const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1); expect(endpoint.bind).toHaveBeenCalledTimes(3); expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint); @@ -31,7 +31,11 @@ describe('Device report', () => { expect(endpoint.configureReporting).toHaveBeenCalledTimes(3); expect(endpoint.configureReporting).toHaveBeenCalledWith('genOnOff', [{"attribute": "onOff", "maximumReportInterval": 300, "minimumReportInterval": 0, "reportableChange": 0}]); expect(endpoint.configureReporting).toHaveBeenCalledWith('genLevelCtrl', [{"attribute": "currentLevel", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]); - expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentX", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentY", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]); + if (colorXY) { + expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentX", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}, {"attribute": "currentY", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]); + } else { + expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]); + } } mockClear = (device) => { @@ -57,7 +61,7 @@ describe('Device report', () => { it('Should configure reporting on startup', async () => { const device = zigbeeHerdsman.devices.bulb_color; const endpoint = device.getEndpoint(1); - expectOnOffBrightnessColorReport(endpoint); + expectOnOffBrightnessColorReport(endpoint, true); }); it('Should configure reporting when receicing message from device which has not been setup yet', async () => { @@ -70,7 +74,7 @@ describe('Device report', () => { const payload = {data, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}; await zigbeeHerdsman.events.message(payload); await flushPromises(); - expectOnOffBrightnessColorReport(endpoint); + expectOnOffBrightnessColorReport(endpoint, false); expect(device.save).toHaveBeenCalledTimes(1); }); @@ -130,7 +134,7 @@ describe('Device report', () => { const payload = {device}; await zigbeeHerdsman.events.deviceAnnounce(payload); await flushPromises(); - expectOnOffBrightnessColorReport(endpoint); + expectOnOffBrightnessColorReport(endpoint, false); }); it('Should not configure reporting on device leave', async () => { @@ -211,4 +215,31 @@ describe('Device report', () => { expect(endpoint.bind).toHaveBeenCalledWith('lightingColorCtrl', coordinatorEndpoint); expect(endpoint.configureReporting).toHaveBeenCalledTimes(3); }); + + it('Should not setup colorTemperature reporting when bulb does not support it and should read colorCapabilities when its not there yet ', async () => { + const device = zigbeeHerdsman.devices.bulb; + const coordinatorEndpoint = zigbeeHerdsman.devices.coordinator.getEndpoint(1); + const endpoint = device.getEndpoint(1); + delete device.meta.reporting; + mockClear(device); + endpoint.getClusterAttributeValue = jest.fn(); + + let count = 0; + endpoint.getClusterAttributeValue.mockImplementation((d) => { + count++; + if (count === 1) return undefined; + return 17; + }); + + const payload = {device}; + await zigbeeHerdsman.events.deviceAnnounce(payload); + await flushPromises(); + expect(endpoint.bind).toHaveBeenCalledTimes(3); + expect(endpoint.bind).toHaveBeenCalledWith('genOnOff', coordinatorEndpoint); + expect(endpoint.bind).toHaveBeenCalledWith('genLevelCtrl', coordinatorEndpoint); + expect(endpoint.bind).toHaveBeenCalledWith('lightingColorCtrl', coordinatorEndpoint); + expect(endpoint.read).toHaveBeenCalledWith('lightingColorCtrl', ['colorCapabilities']) + expect(endpoint.configureReporting).toHaveBeenCalledWith('lightingColorCtrl', [{"attribute": "colorTemperature", "maximumReportInterval": 300, "minimumReportInterval": 3, "reportableChange": 1}]); + expect(endpoint.configureReporting).toHaveBeenCalledTimes(3); + }); }); diff --git a/test/stub/zigbeeHerdsman.js b/test/stub/zigbeeHerdsman.js index df0dd6ae..065b6dc1 100644 --- a/test/stub/zigbeeHerdsman.js +++ b/test/stub/zigbeeHerdsman.js @@ -100,13 +100,13 @@ class Device { const returnDevices = []; -const bulb_color = new Device('Router', '0x000b57fffec6a5b3', 40399, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b3')], true, "Mains (single phase)", "LLC020"); +const bulb_color = new Device('Router', '0x000b57fffec6a5b3', 40399, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b3', [], {lightingColorCtrl: {colorCapabilities: 254}})], true, "Mains (single phase)", "LLC020"); const bulb_color_2 = new Device('Router', '0x000b57fffec6a5b4', 401292, 4107, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b4')], true, "Mains (single phase)", "LLC020"); const bulb_2 = new Device('Router', '0x000b57fffec6a5b7', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b7')], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"); const devices = { 'coordinator': new Device('Coordinator', '0x00124b00120144ae', 0, 0, [new Endpoint(1, [], [])], false), - 'bulb': new Device('Router', '0x000b57fffec6a5b2', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b2')], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"), + 'bulb': new Device('Router', '0x000b57fffec6a5b2', 40369, 4476, [new Endpoint(1, [0,3,4,5,6,8,768,2821,4096], [5,25,32,4096], '0x000b57fffec6a5b2', [], {lightingColorCtrl: {colorCapabilities: 17}})], true, "Mains (single phase)", "TRADFRI bulb E27 WS opal 980lm"), 'bulb_color': bulb_color, 'bulb_2': bulb_2, 'bulb_color_2': bulb_color_2, @@ -139,7 +139,7 @@ const devices = { 'LIVOLO': new Device('Router', '0x0017880104e45560', 6541,4152, [new Endpoint(6, [0, 6], [])], true, "Mains (single phase)", 'TI0001 '), 'tradfri_remote': new Device('EndDevice', '0x90fd9ffffe4b64ae', 33906, 4476, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x90fd9ffffe4b64ae')], true, "Battery", "TRADFRI remote control"), 'roller_shutter': new Device('EndDevice', '0x90fd9ffffe4b64af', 33906, 4476, [new Endpoint(1, [0], [0,3,4,6,8,5], '0x90fd9ffffe4b64af')], true, "Battery", "SCM-R_00.00.03.15TC"), - 'ZNLDP12LM': new Device('Router', '0x90fd9ffffe4b64ax', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64ax')], true, "Mains (single phase)", "lumi.light.aqcn02"), + 'ZNLDP12LM': new Device('Router', '0x90fd9ffffe4b64ax', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64ax', [], {lightingColorCtrl: {colorCapabilities: 254}})], true, "Mains (single phase)", "lumi.light.aqcn02"), 'SP600_OLD': new Device('Router', '0x90fd9ffffe4b64aa', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {seMetering: {"multiplier":1,"divisor":10000}})], true, "Mains (single phase)", "SP600", false, 'Salus', '20160120'), 'SP600_NEW': new Device('Router', '0x90fd9ffffe4b64ab', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {seMetering: {"multiplier":1,"divisor":10000}})], true, "Mains (single phase)", "SP600", false, 'Salus', '20170220'), 'MKS-CM-W5': new Device('Router', '0x90fd9ffffe4b64ac', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {})], true, "Mains (single phase)", "qnazj70", false),