zigbee2mqtt/lib/extension/bind.js
Koen Kanters 92de1ca942
Implement binding feature. #765 #782 (#783)
* Start on bind implementation. https://github.com/Koenkk/zigbee2mqtt/issues/765

* Finish binding implementation.
2018-12-30 22:42:55 +01:00

160 lines
5.6 KiB
JavaScript

const settings = require('../util/settings');
const logger = require('../util/logger');
const utils = require('../util/utils');
const Queue = require('queue');
const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/(bind|unbind)/.+$`);
const foundationCfg = {manufSpec: 0, disDefaultRsp: 0};
const clusteReportCfg = {
6: {direction: 0, attrId: 0, dataType: 16, minRepIntval: 0, maxRepIntval: 1000, repChange: 0},
8: {direction: 0, attrId: 0, dataType: 32, minRepIntval: 0, maxRepIntval: 1000, repChange: 0},
};
const allowedClusters = [
5, // genScenes
6, // genOnOff
8, // genLevelCtrl
768, // lightingColorCtrl
];
class Bind {
constructor(zigbee, mqtt, state, publishDeviceState) {
this.zigbee = zigbee;
this.mqtt = mqtt;
this.state = state;
this.publishDeviceState = publishDeviceState;
// Setup queue
this.queue = new Queue();
this.queue.concurrency = 1;
this.queue.autostart = true;
}
onMQTTConnected() {
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/bind/+`);
this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/unbind/+`);
}
parseTopic(topic) {
if (!topic.match(topicRegex)) {
return null;
}
// Remove base from topic
topic = topic.replace(`${settings.get().mqtt.base_topic}/bridge/`, '');
// Parse type from topic
const type = topic.split('/')[0];
// Remove type from topic
topic = topic.replace(`${type}/`, '');
return {ID: topic, type: type};
}
onMQTTMessage(topic, message) {
topic = this.parseTopic(topic);
if (!topic) {
return false;
}
// Find source; can only be a device.
const sourceEntity = utils.resolveEntity(topic.ID);
const source = this.zigbee.findDevice(sourceEntity.ID);
if (!source) {
logger.error(`Failed to find device '${sourceEntity.ID}'`);
return false;
}
// Find target; can be a device or group.
const targetEntity = utils.resolveEntity(message.toString());
let target = null;
if (targetEntity.type === 'device') {
target = this.zigbee.findDevice(targetEntity.ID);
if (!target) {
logger.error(`Failed to find target device '${targetEntity.ID}'`);
return false;
}
} else if (targetEntity.type === 'group') {
target = targetEntity.ID;
}
// Find which clusters are supported by both the source and target.
// Groups are assumed to support all clusters (as we don't know which devices are in)
let supported = [];
if (targetEntity.type === 'device') {
supported = target.getSimpleDesc().inClusterList.filter((cluster) => {
return allowedClusters.includes(cluster);
});
} else if (targetEntity.type === 'group') {
supported = allowedClusters;
}
const clusters = source.getSimpleDesc().outClusterList.filter((cluster) => {
return supported.includes(cluster);
});
// Bind
clusters.forEach((cluster) => {
this.queue.push((queueCallback) => {
logger.debug(`${topic.type}ing cluster '${cluster}' from ${sourceEntity.ID}' to '${targetEntity.ID}'`);
source[topic.type](cluster, target, (error) => {
if (error) {
logger.error(
`Failed to ${topic.type} cluster '${cluster}' from ${sourceEntity.ID}' to ` +
`'${targetEntity.ID}' (${error})`
);
} else {
logger.info(
`Successfully ${topic.type === 'bind' ? 'bound' : 'unbound'} cluster '${cluster}' from ` +
`${sourceEntity.ID}' to '${targetEntity.ID}'`
);
/**
* As the target device is now directly controllled by the source device
* zigbee2mqtt won't receive any updates of the target device when directly
* changed by the source device.
* Configure reporting on the target device.
* This is not supported for groups yet.
*/
if (targetEntity.type === 'device' && topic.type === 'bind') {
this.setupReporting(target, cluster);
}
}
queueCallback();
});
});
});
}
setupReporting(ep, cluster) {
this.queue.push((queueCallback) => {
if (clusteReportCfg[cluster]) {
ep.foundation(cluster, 'configReport', [clusteReportCfg[cluster]], foundationCfg, (error) => {
if (error) {
logger.error(
`Failed to setup reporting for cluster '${cluster}' of device '${ep.device.ieeeAddr}'`
);
} else {
logger.info(
`Configured reporting for cluster '${cluster}' of device '${ep.device.ieeeAddr}'`
);
}
});
} else {
logger.warn(`Don't know how to setup reporting for '${cluster}', skipping..`);
}
queueCallback();
});
}
}
module.exports = Bind;