From 09e0fe8e3f271bdf4cdeb0078be57baf3c203691 Mon Sep 17 00:00:00 2001 From: jbn Date: Fri, 22 Feb 2019 21:06:24 +0100 Subject: [PATCH] setup automatic attribute change reporting --- lib/extension/deviceReport.js | 77 ++++++++++++++++++-------------- lib/zigbee.js | 84 +++++++++++++++++++++++------------ 2 files changed, 99 insertions(+), 62 deletions(-) diff --git a/lib/extension/deviceReport.js b/lib/extension/deviceReport.js index b7d7c430..a538a9f6 100644 --- a/lib/extension/deviceReport.js +++ b/lib/extension/deviceReport.js @@ -1,14 +1,22 @@ -const settings = require('../util/settings'); -const utils = require('../util/utils'); +const zigbeeShepherdConverters = require('zigbee-shepherd-converters'); const candidates = { - 'genOnOff': ['onOff'], - 'genLevelCtrl': ['currentLevel'], - 'lightingColorCtrl': ['colorTemperature', 'currentX', 'currentY'], + 'genOnOff': { + attrs: ['onOff'], + reportIntervalMin: 3, + reportIntervalMax: 300, + reportableChange: 0, + }, + 'genLevelCtrl': { + attrs: ['currentLevel'], + }, + 'lightingColorCtrl': { + attrs: ['colorTemperature', 'currentX', 'currentY'], + }, }; const reportInterval = { - min: 1, + min: 3, max: 3600, }; @@ -22,41 +30,38 @@ class DeviceReport { this.publishEntityState = publishEntityState; } - shouldReport(ieeeAddr) { - const device = settings.getDevice(ieeeAddr); - return device && device.report; - } - - getEndpoints() { - return this.zigbee.getAllClients() - .filter((d) => this.shouldReport(d.ieeeAddr)) - .map((d) => this.zigbee.getEndpoint(d.ieeeAddr)) - .filter((e) => e); - } - setupReporting(endpoint) { Object.values(endpoint.clusters).filter((c) => c).forEach((c) => { const cluster = c.attrs.cid; if (candidates[cluster]) { - const attributes = candidates[cluster].filter((a) => c.attrs.hasOwnProperty(a)); - this.zigbee.bind(endpoint, cluster); - - attributes.forEach((attribute) => { - this.zigbee.report( - endpoint, - cluster, - attribute, - reportInterval.min, - reportInterval.max, - reportableChange); + const candidate = candidates[cluster]; + const attributeNames = candidate.attrs.filter((a) => c.attrs.hasOwnProperty(a)); + const attributes = []; + attributeNames.forEach((attribute) => { + attributes.push({ + attr: attribute, + min: candidate.hasOwnProperty('reportIntervalMin')? + candidate.reportIntervalMin:reportInterval.min, + max: candidate.hasOwnProperty('reportIntervalMax')? + candidate.reportIntervalMax:reportInterval.max, + change: candidate.hasOwnProperty('reportableChange')? + candidate.reportableChange:reportableChange, + }); }); + this.zigbee.report(endpoint, cluster, attributes); } }); } onZigbeeStarted() { - const endpoints = this.getEndpoints(); - endpoints.forEach((e) => this.setupReporting(e)); + this.zigbee.getAllClients().forEach((device) => { + const mappedDevice = zigbeeShepherdConverters.findByZigbeeModel(device.modelId); + + if (mappedDevice && mappedDevice.report) { + const endpoint = (typeof(mappedDevice.report)=='number')?this.zigbee.getEndpoint(device.ieeeAddr, mappedDevice.report):this.zigbee.getEndpoint(device.ieeeAddr); + this.setupReporting(endpoint); + } + }); } onZigbeeMessage(message, device, mappedDevice) { @@ -65,9 +70,13 @@ class DeviceReport { // Ikea TRADFRI tend to forget their reporting after powered off. // Re-setup reporting. // https://github.com/Koenkk/zigbee2mqtt/issues/966 - if (device && message.type === 'endDeviceAnnce' && utils.isIkeaTradfriDevice(device) && - this.shouldReport(device.ieeeAddr)) { - const endpoint = this.zigbee.getEndpoint(device.ieeeAddr); + // The mappedDevice.report property is now used as boolean + // the property might contain more information about how to + // configure reporting. For instance the endpointIds to use. + if (device && mappedDevice + && (message.type=='endDeviceAnnce' || message.type=='devIncoming') + && mappedDevice.report) { + const endpoint = (typeof(mappedDevice.report)=='number')?this.zigbee.getEndpoint(device.ieeeAddr, mappedDevice.report):this.zigbee.getEndpoint(device.ieeeAddr); if (endpoint) { this.setupReporting(endpoint); } diff --git a/lib/zigbee.js b/lib/zigbee.js index d74aac2a..73db04b7 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -3,8 +3,8 @@ const logger = require('./util/logger'); const settings = require('./util/settings'); const data = require('./util/data'); const utils = require('./util/utils'); -const ZigbeeQueue = require('./util/zigbeeQueue'); const cieApp = require('./zapp/cie'); +const Queue = require('queue'); const zclId = require('zcl-id'); const advancedSettings = settings.get().advanced; @@ -32,6 +32,8 @@ const defaultCfg = { disDefaultRsp: 0, }; +const delay = 170; + logger.debug(`Using zigbee-shepherd with settings: '${JSON.stringify(shepherdSettings)}'`); class Zigbee { @@ -41,7 +43,8 @@ class Zigbee { this.onError = this.onError.bind(this); this.messageHandler = null; - this.queue = new ZigbeeQueue(); + this.queue = new Queue(); + this.queue.concurrency = 1; } start(messageHandler, callback) { @@ -51,7 +54,7 @@ class Zigbee { this.shepherd.start((error) => { if (error) { - logger.info('Error while starting zigbee-shepherd, attempting to fix... (takes 60 seconds)'); + logger.info('Error while starting zigbee-shepherd, attemping to fix... (takes 60 seconds)'); this.shepherd.controller._znp.close((() => null)); setTimeout(() => { @@ -117,6 +120,7 @@ class Zigbee { // Wait some time before we start the queue, many calls skip this queue which hangs the stick setTimeout(() => { + this.queue.autostart = true; this.queue.start(); }, 2000); @@ -236,7 +240,7 @@ class Zigbee { return; } - this.queue.push(entityID, (queueCallback) => { + this.queue.push((queueCallback) => { logger.info( `Zigbee publish to ${entityType} '${entityID}', ${cid} - ${cmd} - ` + `${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}` @@ -253,8 +257,6 @@ class Zigbee { if (callback) { callback(error, rsp); } - - queueCallback(error); }; if (cmdType === 'functional' && entity.functional) { @@ -264,6 +266,8 @@ class Zigbee { } else { logger.error(`Unknown zigbee publish cmdType ${cmdType}`); } + + setTimeout(() => queueCallback(), delay); }); } @@ -271,7 +275,7 @@ class Zigbee { const device = this.shepherd._findDevByAddr(ieeeAddr); if (device) { - this.queue.push(ieeeAddr, (queueCallback) => { + this.queue.push((queueCallback) => { logger.debug(`Ping ${ieeeAddr}`); this.shepherd.controller.checkOnline(device, (error) => { if (error) { @@ -283,9 +287,9 @@ class Zigbee { if (cb) { cb(error); } - - queueCallback(error); }); + + setTimeout(() => queueCallback(), delay); }); } } @@ -293,7 +297,7 @@ class Zigbee { bind(ep, cluster, target=this.getCoordinator()) { const log = `for ${ep.device.ieeeAddr} - ${cluster}`; - this.queue.push(ep.device.ieeeAddr, (queueCallback) => { + this.queue.push((queueCallback) => { logger.debug(`Setup binding ${log}`); ep.bind(cluster, target, (error) => { if (error) { @@ -301,29 +305,53 @@ class Zigbee { } else { logger.debug(`Successfully setup binding ${log}`); } - - queueCallback(error); }); + + setTimeout(() => queueCallback(), delay); }); } - report(ep, cluster, attribute, min, max, change) { - const attrId = zclId.attr(cluster, attribute).value; - const dataType = zclId.attrType(cluster, attribute).value; - const cfg = {direction: 0, attrId, dataType, minRepIntval: min, maxRepIntval: max, repChange: change}; - const log = `for ${ep.device.ieeeAddr} - ${cluster} - ${attribute}`; - - this.queue.push(ep.device.ieeeAddr, (queueCallback) => { - logger.debug(`Setup reporting ${log}`); - ep.foundation(cluster, 'configReport', [cfg], defaultCfg, (error) => { - if (error) { - logger.error(`Failed to setup reporting ${log} - (${error})`); - } else { - logger.debug(`Successfully setup reporting ${log}`); - } - - queueCallback(error); + /* setup reporting. + * attributes is an array of attribute objects. + * each attribute object contains the following properties: + * attr the attribute name, + * min the minimal time between reports in seconds, + * max the maximum time between reports in seconds, + * change the minimum amount of change before sending a report + */ + report(ep, cluster, attributes) { + const cfgArr = []; + for(let i=0; i { + logger.debug(`Setup reporting ${log}`); + ep.bind(cluster, this.getCoordinator(), (error) => { + if (error) { + logger.error(`Failed to bind for reporting ${log} - (${error})`); + } else { + ep.foundation(cluster, 'configReport', cfgArr, defaultCfg, (error) => { + if (error) { + logger.error(`Failed to setup reporting ${log} - (${error})`); + } else { + logger.debug(`Successfully setup reporting ${log}`); + } + }); + } + }); + setTimeout(() => queueCallback(), delay); }); } }