setup automatic attribute change reporting

This commit is contained in:
jbn 2019-02-22 21:06:24 +01:00 committed by Koen Kanters
parent fdf97180b0
commit 09e0fe8e3f
2 changed files with 99 additions and 62 deletions

View File

@ -1,14 +1,22 @@
const settings = require('../util/settings'); const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
const utils = require('../util/utils');
const candidates = { const candidates = {
'genOnOff': ['onOff'], 'genOnOff': {
'genLevelCtrl': ['currentLevel'], attrs: ['onOff'],
'lightingColorCtrl': ['colorTemperature', 'currentX', 'currentY'], reportIntervalMin: 3,
reportIntervalMax: 300,
reportableChange: 0,
},
'genLevelCtrl': {
attrs: ['currentLevel'],
},
'lightingColorCtrl': {
attrs: ['colorTemperature', 'currentX', 'currentY'],
},
}; };
const reportInterval = { const reportInterval = {
min: 1, min: 3,
max: 3600, max: 3600,
}; };
@ -22,41 +30,38 @@ class DeviceReport {
this.publishEntityState = publishEntityState; 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) { setupReporting(endpoint) {
Object.values(endpoint.clusters).filter((c) => c).forEach((c) => { Object.values(endpoint.clusters).filter((c) => c).forEach((c) => {
const cluster = c.attrs.cid; const cluster = c.attrs.cid;
if (candidates[cluster]) { if (candidates[cluster]) {
const attributes = candidates[cluster].filter((a) => c.attrs.hasOwnProperty(a)); const candidate = candidates[cluster];
this.zigbee.bind(endpoint, cluster); const attributeNames = candidate.attrs.filter((a) => c.attrs.hasOwnProperty(a));
const attributes = [];
attributes.forEach((attribute) => { attributeNames.forEach((attribute) => {
this.zigbee.report( attributes.push({
endpoint, attr: attribute,
cluster, min: candidate.hasOwnProperty('reportIntervalMin')?
attribute, candidate.reportIntervalMin:reportInterval.min,
reportInterval.min, max: candidate.hasOwnProperty('reportIntervalMax')?
reportInterval.max, candidate.reportIntervalMax:reportInterval.max,
reportableChange); change: candidate.hasOwnProperty('reportableChange')?
candidate.reportableChange:reportableChange,
});
}); });
this.zigbee.report(endpoint, cluster, attributes);
} }
}); });
} }
onZigbeeStarted() { onZigbeeStarted() {
const endpoints = this.getEndpoints(); this.zigbee.getAllClients().forEach((device) => {
endpoints.forEach((e) => this.setupReporting(e)); 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) { onZigbeeMessage(message, device, mappedDevice) {
@ -65,9 +70,13 @@ class DeviceReport {
// Ikea TRADFRI tend to forget their reporting after powered off. // Ikea TRADFRI tend to forget their reporting after powered off.
// Re-setup reporting. // Re-setup reporting.
// https://github.com/Koenkk/zigbee2mqtt/issues/966 // https://github.com/Koenkk/zigbee2mqtt/issues/966
if (device && message.type === 'endDeviceAnnce' && utils.isIkeaTradfriDevice(device) && // The mappedDevice.report property is now used as boolean
this.shouldReport(device.ieeeAddr)) { // the property might contain more information about how to
const endpoint = this.zigbee.getEndpoint(device.ieeeAddr); // 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) { if (endpoint) {
this.setupReporting(endpoint); this.setupReporting(endpoint);
} }

View File

@ -3,8 +3,8 @@ const logger = require('./util/logger');
const settings = require('./util/settings'); const settings = require('./util/settings');
const data = require('./util/data'); const data = require('./util/data');
const utils = require('./util/utils'); const utils = require('./util/utils');
const ZigbeeQueue = require('./util/zigbeeQueue');
const cieApp = require('./zapp/cie'); const cieApp = require('./zapp/cie');
const Queue = require('queue');
const zclId = require('zcl-id'); const zclId = require('zcl-id');
const advancedSettings = settings.get().advanced; const advancedSettings = settings.get().advanced;
@ -32,6 +32,8 @@ const defaultCfg = {
disDefaultRsp: 0, disDefaultRsp: 0,
}; };
const delay = 170;
logger.debug(`Using zigbee-shepherd with settings: '${JSON.stringify(shepherdSettings)}'`); logger.debug(`Using zigbee-shepherd with settings: '${JSON.stringify(shepherdSettings)}'`);
class Zigbee { class Zigbee {
@ -41,7 +43,8 @@ class Zigbee {
this.onError = this.onError.bind(this); this.onError = this.onError.bind(this);
this.messageHandler = null; this.messageHandler = null;
this.queue = new ZigbeeQueue(); this.queue = new Queue();
this.queue.concurrency = 1;
} }
start(messageHandler, callback) { start(messageHandler, callback) {
@ -51,7 +54,7 @@ class Zigbee {
this.shepherd.start((error) => { this.shepherd.start((error) => {
if (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)); this.shepherd.controller._znp.close((() => null));
setTimeout(() => { setTimeout(() => {
@ -117,6 +120,7 @@ class Zigbee {
// Wait some time before we start the queue, many calls skip this queue which hangs the stick // Wait some time before we start the queue, many calls skip this queue which hangs the stick
setTimeout(() => { setTimeout(() => {
this.queue.autostart = true;
this.queue.start(); this.queue.start();
}, 2000); }, 2000);
@ -236,7 +240,7 @@ class Zigbee {
return; return;
} }
this.queue.push(entityID, (queueCallback) => { this.queue.push((queueCallback) => {
logger.info( logger.info(
`Zigbee publish to ${entityType} '${entityID}', ${cid} - ${cmd} - ` + `Zigbee publish to ${entityType} '${entityID}', ${cid} - ${cmd} - ` +
`${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}` `${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}`
@ -253,8 +257,6 @@ class Zigbee {
if (callback) { if (callback) {
callback(error, rsp); callback(error, rsp);
} }
queueCallback(error);
}; };
if (cmdType === 'functional' && entity.functional) { if (cmdType === 'functional' && entity.functional) {
@ -264,6 +266,8 @@ class Zigbee {
} else { } else {
logger.error(`Unknown zigbee publish cmdType ${cmdType}`); logger.error(`Unknown zigbee publish cmdType ${cmdType}`);
} }
setTimeout(() => queueCallback(), delay);
}); });
} }
@ -271,7 +275,7 @@ class Zigbee {
const device = this.shepherd._findDevByAddr(ieeeAddr); const device = this.shepherd._findDevByAddr(ieeeAddr);
if (device) { if (device) {
this.queue.push(ieeeAddr, (queueCallback) => { this.queue.push((queueCallback) => {
logger.debug(`Ping ${ieeeAddr}`); logger.debug(`Ping ${ieeeAddr}`);
this.shepherd.controller.checkOnline(device, (error) => { this.shepherd.controller.checkOnline(device, (error) => {
if (error) { if (error) {
@ -283,9 +287,9 @@ class Zigbee {
if (cb) { if (cb) {
cb(error); cb(error);
} }
queueCallback(error);
}); });
setTimeout(() => queueCallback(), delay);
}); });
} }
} }
@ -293,7 +297,7 @@ class Zigbee {
bind(ep, cluster, target=this.getCoordinator()) { bind(ep, cluster, target=this.getCoordinator()) {
const log = `for ${ep.device.ieeeAddr} - ${cluster}`; const log = `for ${ep.device.ieeeAddr} - ${cluster}`;
this.queue.push(ep.device.ieeeAddr, (queueCallback) => { this.queue.push((queueCallback) => {
logger.debug(`Setup binding ${log}`); logger.debug(`Setup binding ${log}`);
ep.bind(cluster, target, (error) => { ep.bind(cluster, target, (error) => {
if (error) { if (error) {
@ -301,29 +305,53 @@ class Zigbee {
} else { } else {
logger.debug(`Successfully setup binding ${log}`); logger.debug(`Successfully setup binding ${log}`);
} }
queueCallback(error);
}); });
setTimeout(() => queueCallback(), delay);
}); });
} }
report(ep, cluster, attribute, min, max, change) { /* setup reporting.
const attrId = zclId.attr(cluster, attribute).value; * attributes is an array of attribute objects.
const dataType = zclId.attrType(cluster, attribute).value; * each attribute object contains the following properties:
const cfg = {direction: 0, attrId, dataType, minRepIntval: min, maxRepIntval: max, repChange: change}; * attr the attribute name,
const log = `for ${ep.device.ieeeAddr} - ${cluster} - ${attribute}`; * min the minimal time between reports in seconds,
* max the maximum time between reports in seconds,
this.queue.push(ep.device.ieeeAddr, (queueCallback) => { * change the minimum amount of change before sending a report
logger.debug(`Setup reporting ${log}`); */
ep.foundation(cluster, 'configReport', [cfg], defaultCfg, (error) => { report(ep, cluster, attributes) {
if (error) { const cfgArr = [];
logger.error(`Failed to setup reporting ${log} - (${error})`); for(let i=0; i<attributes.length; i++) {
} else { const attribute = attributes[i];
logger.debug(`Successfully setup reporting ${log}`); const attrId = zclId.attr(cluster, attribute.attr).value;
} const dataType = zclId.attrType(cluster, attribute.attr).value;
cfgArr.push({
queueCallback(error); direction: 0,
attrId,
dataType,
minRepIntval: attribute.min,
maxRepIntval: attribute.max,
repChange: attribute.change,
}); });
}
const log=`for ${ep.device.ieeeAddr} - ${cluster} - ${attributes.length}`;
this.queue.push((queueCallback) => {
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);
}); });
} }
} }