zigbee2mqtt/lib/zigbee.js

265 lines
8.7 KiB
JavaScript
Raw Normal View History

2018-04-18 09:25:40 -07:00
const ZShepherd = require('zigbee-shepherd');
const logger = require('./util/logger');
const settings = require('./util/settings');
2018-05-11 20:04:15 -07:00
const data = require('./util/data');
const zclPacket = require('zcl-packet');
const utils = require('./util/utils');
2018-04-18 09:25:40 -07:00
const advancedSettings = settings.get().advanced;
2018-04-18 09:25:40 -07:00
const shepherdSettings = {
net: {
panId: advancedSettings && advancedSettings.pan_id ? advancedSettings.pan_id : 0x1a62,
channelList: [advancedSettings && advancedSettings.channel ? advancedSettings.channel : 11],
},
2018-05-17 08:48:41 -07:00
dbPath: data.joinPath('database.db'),
2018-06-14 12:37:19 -07:00
sp: {
2018-08-05 08:54:07 -07:00
baudRate: advancedSettings && advancedSettings.baudrate ? advancedSettings.baudrate : 115200,
2018-06-14 12:37:19 -07:00
rtscts: advancedSettings && (typeof(advancedSettings.rtscts) === 'boolean') ? advancedSettings.rtscts : true,
},
2018-04-18 09:25:40 -07:00
};
2018-06-14 12:37:19 -07:00
logger.debug(`Using zigbee-shepherd with settings: '${JSON.stringify(shepherdSettings)}'`);
2018-04-18 09:25:40 -07:00
class Zigbee {
constructor(onMessage) {
this.onMessage = onMessage;
2018-04-18 09:25:40 -07:00
this.handleReady = this.handleReady.bind(this);
this.handleMessage = this.handleMessage.bind(this);
2018-05-17 08:48:41 -07:00
this.handleError = this.handleError.bind(this);
2018-04-18 09:25:40 -07:00
}
start(callback) {
2018-04-18 09:25:40 -07:00
logger.info(`Starting zigbee-shepherd`);
this.shepherd = new ZShepherd(settings.get().serial.port, shepherdSettings);
this.shepherd.start((error) => {
if (error) {
logger.info('Error while starting zigbee-shepherd, attemping to fix... (takes 60 seconds)');
this.shepherd.controller._znp.close((() => null));
setTimeout(() => {
2018-06-04 08:36:46 -07:00
logger.info(`Starting zigbee-shepherd`);
this.shepherd.start((error) => {
if (error) {
logger.error('Error while starting zigbee-shepherd!');
logger.error(
'Press the reset button on the stick (the one closest to the USB) and start again'
);
callback(error);
} else {
this._logStartupInfo();
callback(null);
}
});
2018-06-04 08:36:46 -07:00
}, 60 * 1000);
2018-04-18 09:25:40 -07:00
} else {
this._logStartupInfo();
callback(null);
2018-04-18 09:25:40 -07:00
}
});
// Register callbacks.
this.shepherd.on('ready', this.handleReady);
this.shepherd.on('ind', this.handleMessage);
this.shepherd.on('error', this.handleError);
2018-04-18 09:25:40 -07:00
}
_logStartupInfo() {
logger.info('zigbee-shepherd started');
logger.info(`Coordinator firmware version: '${this.shepherd.info().firmware.revision}'`);
logger.debug(`zigbee-shepherd info: ${JSON.stringify(this.shepherd.info())}`);
}
2018-05-21 02:49:02 -07:00
softReset(callback) {
this.shepherd.reset('soft', callback);
}
2018-04-18 09:25:40 -07:00
stop(callback) {
this.shepherd.stop((error) => {
2018-05-17 08:20:46 -07:00
logger.info('zigbee-shepherd stopped');
2018-04-18 09:25:40 -07:00
callback(error);
});
}
handleReady() {
// Set all Xiaomi devices to be online, so shepherd won't try
// to query info from devices (which would fail because they go to sleep).
const devices = this.getAllClients();
2018-06-14 09:19:54 -07:00
devices.forEach((d) => {
if (utils.isXiaomiDevice(d)) {
2018-06-14 09:19:54 -07:00
const device = this.shepherd.find(d.ieeeAddr, 1);
if (device) {
device.getDevice().update({
status: 'online',
joinTime: Math.floor(Date.now() / 1000),
});
}
2018-04-18 09:25:40 -07:00
}
});
// Check if we have to turn off the led
2018-07-21 12:13:28 -07:00
if (settings.get().serial.disable_led) {
this.shepherd.controller.request('UTIL', 'ledControl', {ledid: 3, mode: 0});
}
logger.info('zigbee-shepherd ready');
}
2018-04-18 09:25:40 -07:00
handleError(message) {
2018-05-17 08:48:41 -07:00
// This event may appear if zigbee-shepherd cannot decode bad packets (invalid checksum).
logger.error(message);
}
permitJoin(permit) {
if (permit) {
logger.info('Zigbee: allowing new devices to join.');
} else {
logger.info('Zigbee: disabling joining new devices.');
2018-04-18 09:25:40 -07:00
}
this.shepherd.permitJoin(permit ? 255 : 0, (error) => {
2018-04-18 09:25:40 -07:00
if (error) {
logger.info(error);
}
});
}
getAllClients() {
return this.shepherd.list().filter((device) => device.type !== 'Coordinator');
}
removeDevice(deviceID, callback) {
this.shepherd.remove(deviceID, (error) => {
if (error) {
logger.warn(`Failed to remove '${deviceID}', trying force remove...`);
this._forceRemove(deviceID, callback);
2018-06-06 12:13:32 -07:00
} else {
callback(null);
2018-06-06 12:13:32 -07:00
}
});
}
_forceRemove(deviceID, callback) {
2018-06-06 12:13:32 -07:00
const device = this.shepherd._findDevByAddr(deviceID);
2018-06-07 10:41:11 -07:00
if (device) {
return this.shepherd._unregisterDev(device, (error) => callback(error));
2018-06-07 10:41:11 -07:00
} else {
logger.warn(`Could not find ${deviceID} for force removal`);
callback(true);
2018-06-07 10:41:11 -07:00
}
}
2018-06-06 12:19:50 -07:00
2018-05-21 02:49:02 -07:00
ping(deviceID) {
let friendlyName = 'unknown';
2018-05-21 02:49:02 -07:00
const device = this.shepherd._findDevByAddr(deviceID);
const ieeeAddr = device.ieeeAddr;
if (settings.getDevice(ieeeAddr)) {
friendlyName = settings.getDevice(ieeeAddr).friendly_name;
}
2018-08-13 10:17:46 -07:00
2018-05-21 02:49:02 -07:00
if (device) {
// Note: checkOnline has the callback argument but does not call callback
logger.debug(`Check online ${friendlyName} ${deviceID}`);
2018-05-21 02:49:02 -07:00
this.shepherd.controller.checkOnline(device);
}
}
2018-04-18 09:25:40 -07:00
handleMessage(message) {
if (this.onMessage) {
this.onMessage(message);
}
}
2018-04-27 14:58:46 -07:00
getDevice(deviceID) {
return this.shepherd.list().find((d) => d.ieeeAddr === deviceID);
}
getCoordinator() {
const device = this.shepherd.list().find((d) => d.type === 'Coordinator');
return this.shepherd.find(device.ieeeAddr, 1);
}
publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback) {
2018-05-30 13:28:08 -07:00
const device = this._findDevice(deviceID, ep);
if (!device) {
logger.error(`Zigbee cannot publish message to device because '${deviceID}' not known by zigbee-shepherd`);
return;
}
logger.info(
`Zigbee publish to '${deviceID}', ${cid} - ${cmd} - ` +
`${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}`
);
2018-09-13 23:33:28 -07:00
2018-09-19 11:59:30 -07:00
const callback_ = (error) => {
2018-09-11 12:28:30 -07:00
if (error) {
logger.error(
`Zigbee publish to '${deviceID}', ${cid} - ${cmd} - ${JSON.stringify(zclData)} ` +
`- ${JSON.stringify(cfg)} - ${ep} ` +
2018-09-11 12:28:30 -07:00
`failed with error ${error}`);
}
callback(error);
2018-09-19 11:59:30 -07:00
};
if (type === 'functional') {
device.functional(cid, cmd, zclData, cfg, callback_);
2018-09-19 11:59:30 -07:00
} else if (type === 'foundation') {
device.foundation(cid, cmd, [zclData], cfg, callback_);
2018-09-19 11:59:30 -07:00
} else {
logger.error(`Unknown zigbee publish type ${type}`);
}
2018-05-30 13:28:08 -07:00
}
2018-05-30 13:28:08 -07:00
read(deviceID, cid, attr, ep, callback) {
const device = this._findDevice(deviceID, ep);
2018-04-18 09:25:40 -07:00
if (!device) {
2018-05-30 13:28:08 -07:00
logger.error(`Zigbee cannot read attribute from device because '${deviceID}' not known by zigbee-shepherd`);
2018-04-18 12:55:00 -07:00
return;
2018-04-18 09:25:40 -07:00
}
2018-05-30 13:28:08 -07:00
device.read(cid, attr, callback);
}
networkScan(callback) {
logger.info('Starting network scan...');
this.shepherd.lqiScan().then((result) => {
logger.info('Network scan completed');
callback(result);
});
}
registerOnAfIncomingMsg(ieeeAddr, ep) {
const device = this._findDevice(ieeeAddr, ep);
device.onAfIncomingMsg = (message) => {
// Parse the message
zclPacket.parse(message.data, message.clusterid, (error, zclData) => {
const message = {
endpoints: [device],
data: zclData,
};
this.handleMessage(message);
});
};
}
2018-05-30 13:28:08 -07:00
_findDevice(deviceID, ep) {
// Find device in zigbee-shepherd
let device = this.getDevice(deviceID);
if (!device || !device.epList || !device.epList.length) {
logger.error(`Zigbee cannot determine endpoint for '${deviceID}'`);
return null;
}
ep = ep ? ep : device.epList[0];
device = this.shepherd.find(deviceID, ep);
return device;
2018-04-18 09:25:40 -07:00
}
}
module.exports = Zigbee;