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');
|
2018-10-02 12:15:12 -07:00
|
|
|
const utils = require('./util/utils');
|
2018-12-04 12:09:52 -07:00
|
|
|
const cieApp = require('./zapp/cie');
|
2018-04-18 09:25:40 -07:00
|
|
|
|
2018-05-24 11:19:04 -07:00
|
|
|
const advancedSettings = settings.get().advanced;
|
2018-04-18 09:25:40 -07:00
|
|
|
const shepherdSettings = {
|
2018-05-17 00:52:28 -07:00
|
|
|
net: {
|
2018-11-16 12:23:11 -07:00
|
|
|
panId: advancedSettings.pan_id,
|
|
|
|
channelList: [advancedSettings.channel],
|
2018-05-17 00:52:28 -07:00
|
|
|
},
|
2018-05-17 08:48:41 -07:00
|
|
|
dbPath: data.joinPath('database.db'),
|
2018-06-14 12:37:19 -07:00
|
|
|
sp: {
|
2018-11-16 12:23:11 -07:00
|
|
|
baudRate: advancedSettings.baudrate,
|
|
|
|
rtscts: advancedSettings.rtscts,
|
2018-06-14 12:37:19 -07:00
|
|
|
},
|
2018-04-18 09:25:40 -07:00
|
|
|
};
|
|
|
|
|
2018-12-21 16:07:53 -07:00
|
|
|
const defaultCfg = {
|
|
|
|
manufSpec: 0,
|
|
|
|
disDefaultRsp: 0,
|
|
|
|
};
|
|
|
|
|
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 {
|
2018-11-16 12:23:11 -07:00
|
|
|
constructor() {
|
|
|
|
this.onReady = this.onReady.bind(this);
|
|
|
|
this.onMessage = this.onMessage.bind(this);
|
|
|
|
this.onError = this.onError.bind(this);
|
|
|
|
this.messageHandler = null;
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
start(messageHandler, callback) {
|
2018-04-18 09:25:40 -07:00
|
|
|
logger.info(`Starting zigbee-shepherd`);
|
2018-11-16 12:23:11 -07:00
|
|
|
this.messageHandler = messageHandler;
|
2018-04-18 09:25:40 -07:00
|
|
|
this.shepherd = new ZShepherd(settings.get().serial.port, shepherdSettings);
|
|
|
|
|
|
|
|
this.shepherd.start((error) => {
|
|
|
|
if (error) {
|
2018-06-28 10:49:16 -07:00
|
|
|
logger.info('Error while starting zigbee-shepherd, attemping to fix... (takes 60 seconds)');
|
2018-06-01 12:04:48 -07:00
|
|
|
this.shepherd.controller._znp.close((() => null));
|
|
|
|
|
|
|
|
setTimeout(() => {
|
2018-06-04 08:36:46 -07:00
|
|
|
logger.info(`Starting zigbee-shepherd`);
|
2018-06-01 12:04:48 -07:00
|
|
|
this.shepherd.start((error) => {
|
|
|
|
if (error) {
|
|
|
|
logger.error('Error while starting zigbee-shepherd!');
|
2018-06-28 10:49:16 -07:00
|
|
|
logger.error(
|
|
|
|
'Press the reset button on the stick (the one closest to the USB) and start again'
|
|
|
|
);
|
2018-06-01 12:04:48 -07:00
|
|
|
callback(error);
|
|
|
|
} else {
|
2018-11-16 12:23:11 -07:00
|
|
|
this.logStartupInfo();
|
2018-06-01 12:04:48 -07:00
|
|
|
callback(null);
|
|
|
|
}
|
|
|
|
});
|
2018-11-16 12:23:11 -07:00
|
|
|
}, utils.secondsToMilliseconds(60));
|
2018-04-18 09:25:40 -07:00
|
|
|
} else {
|
2018-11-16 12:23:11 -07:00
|
|
|
this.logStartupInfo();
|
2018-06-01 12:04:48 -07:00
|
|
|
callback(null);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Register callbacks.
|
2018-11-16 12:23:11 -07:00
|
|
|
this.shepherd.on('ready', this.onReady);
|
|
|
|
this.shepherd.on('ind', this.onMessage);
|
|
|
|
this.shepherd.on('error', this.onError);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
logStartupInfo() {
|
2018-07-05 11:06:38 -07:00
|
|
|
logger.info('zigbee-shepherd started');
|
2018-08-07 12:24:10 -07:00
|
|
|
logger.info(`Coordinator firmware version: '${this.shepherd.info().firmware.revision}'`);
|
2018-07-05 11:06:38 -07:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
onReady() {
|
2018-12-04 12:09:52 -07:00
|
|
|
// Mount cieApp
|
|
|
|
this.shepherd.mount(cieApp, (err, epId) => {
|
|
|
|
if (!err) {
|
|
|
|
logger.debug(`Mounted the cieApp (epId {epId})`);
|
|
|
|
} else {
|
|
|
|
logger.error(`Failed to mount the cieApp`);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-10-02 12:15:12 -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});
|
|
|
|
}
|
|
|
|
|
2018-05-16 10:29:47 -07:00
|
|
|
logger.info('zigbee-shepherd ready');
|
2018-04-24 10:30:56 -07:00
|
|
|
}
|
2018-04-18 09:25:40 -07:00
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
onError(message) {
|
2018-05-17 08:48:41 -07:00
|
|
|
// This event may appear if zigbee-shepherd cannot decode bad packets (invalid checksum).
|
2018-05-17 00:52:28 -07:00
|
|
|
logger.error(message);
|
|
|
|
}
|
|
|
|
|
2018-12-01 07:30:59 -07:00
|
|
|
permitJoin(permit, callback) {
|
2018-04-24 10:30:56 -07:00
|
|
|
if (permit) {
|
2018-09-23 02:16:45 -07:00
|
|
|
logger.info('Zigbee: allowing new devices to join.');
|
2018-04-24 10:30:56 -07:00
|
|
|
} else {
|
2018-09-23 02:16:45 -07:00
|
|
|
logger.info('Zigbee: disabling joining new devices.');
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
|
2018-09-23 02:16:45 -07:00
|
|
|
this.shepherd.permitJoin(permit ? 255 : 0, (error) => {
|
2018-04-18 09:25:40 -07:00
|
|
|
if (error) {
|
|
|
|
logger.info(error);
|
|
|
|
}
|
2018-12-01 07:30:59 -07:00
|
|
|
|
|
|
|
if (callback) {
|
|
|
|
callback();
|
|
|
|
}
|
2018-04-18 09:25:40 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-12-01 07:30:59 -07:00
|
|
|
getPermitJoin() {
|
|
|
|
return this.shepherd.controller._permitJoinTime === 255;
|
|
|
|
}
|
|
|
|
|
2018-04-23 09:17:47 -07:00
|
|
|
getAllClients() {
|
2018-10-07 12:46:54 -07:00
|
|
|
return this.getDevices().filter((device) => device.type !== 'Coordinator');
|
2018-04-23 09:17:47 -07:00
|
|
|
}
|
|
|
|
|
2018-06-09 03:27:04 -07:00
|
|
|
removeDevice(deviceID, callback) {
|
|
|
|
this.shepherd.remove(deviceID, (error) => {
|
|
|
|
if (error) {
|
|
|
|
logger.warn(`Failed to remove '${deviceID}', trying force remove...`);
|
2018-11-16 12:23:11 -07:00
|
|
|
this.forceRemove(deviceID, callback);
|
2018-06-06 12:13:32 -07:00
|
|
|
} else {
|
2018-06-09 03:27:04 -07:00
|
|
|
callback(null);
|
2018-06-06 12:13:32 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
forceRemove(deviceID, callback) {
|
2018-06-06 12:13:32 -07:00
|
|
|
const device = this.shepherd._findDevByAddr(deviceID);
|
2018-06-09 03:27:04 -07:00
|
|
|
|
2018-06-07 10:41:11 -07:00
|
|
|
if (device) {
|
2018-06-09 03:27:04 -07:00
|
|
|
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`);
|
2018-06-09 03:27:04 -07:00
|
|
|
callback(true);
|
2018-06-07 10:41:11 -07:00
|
|
|
}
|
2018-06-04 01:55:00 -07:00
|
|
|
}
|
2018-06-06 12:19:50 -07:00
|
|
|
|
2018-05-21 02:49:02 -07:00
|
|
|
ping(deviceID) {
|
2018-08-10 10:45:09 -07:00
|
|
|
let friendlyName = 'unknown';
|
2018-05-21 02:49:02 -07:00
|
|
|
const device = this.shepherd._findDevByAddr(deviceID);
|
2018-08-10 10:45:09 -07:00
|
|
|
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
|
2018-08-10 10:45:09 -07:00
|
|
|
logger.debug(`Check online ${friendlyName} ${deviceID}`);
|
2018-05-21 02:49:02 -07:00
|
|
|
this.shepherd.controller.checkOnline(device);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
onMessage(message) {
|
|
|
|
if (this.messageHandler) {
|
|
|
|
this.messageHandler(message);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
getDevices() {
|
|
|
|
return this.shepherd.list();
|
|
|
|
}
|
|
|
|
|
2018-10-23 11:39:48 -07:00
|
|
|
getDevice(ieeeAddr) {
|
|
|
|
return this.getDevices().find((d) => d.ieeeAddr === ieeeAddr);
|
2018-04-27 14:58:46 -07:00
|
|
|
}
|
|
|
|
|
2018-06-04 12:36:51 -07:00
|
|
|
getCoordinator() {
|
2018-10-07 12:46:54 -07:00
|
|
|
const device = this.getDevices().find((d) => d.type === 'Coordinator');
|
2018-06-04 12:36:51 -07:00
|
|
|
return this.shepherd.find(device.ieeeAddr, 1);
|
|
|
|
}
|
|
|
|
|
2018-12-21 16:07:53 -07:00
|
|
|
getGroup(ID) {
|
|
|
|
return this.shepherd.getGroup(ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
publish(entityID, entityType, cid, cmd, cmdType, zclData, cfg=defaultCfg, ep, callback) {
|
|
|
|
let entity = null;
|
|
|
|
if (entityType === 'device') {
|
|
|
|
entity = this.findDevice(entityID, ep);
|
|
|
|
} else if (entityType === 'group') {
|
|
|
|
entity = this.getGroup(entityID);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!entity) {
|
|
|
|
logger.error(
|
|
|
|
`Zigbee cannot publish message to ${entityType} because '${entityID}' not known by zigbee-shepherd`
|
|
|
|
);
|
2018-04-18 12:57:39 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-02 12:15:12 -07:00
|
|
|
logger.info(
|
2018-12-21 16:07:53 -07:00
|
|
|
`Zigbee publish to ${entityType} '${entityID}', ${cid} - ${cmd} - ` +
|
2018-10-02 12:15:12 -07:00
|
|
|
`${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}`
|
|
|
|
);
|
2018-09-13 23:33:28 -07:00
|
|
|
|
2018-11-05 13:55:30 -07:00
|
|
|
const callback_ = (error, rsp) => {
|
2018-09-11 12:28:30 -07:00
|
|
|
if (error) {
|
|
|
|
logger.error(
|
2018-12-21 16:07:53 -07:00
|
|
|
`Zigbee publish to ${entityType} '${entityID}', ${cid} - ${cmd} - ${JSON.stringify(zclData)} ` +
|
2018-10-02 12:15:12 -07:00
|
|
|
`- ${JSON.stringify(cfg)} - ${ep} ` +
|
2018-09-11 12:28:30 -07:00
|
|
|
`failed with error ${error}`);
|
|
|
|
}
|
|
|
|
|
2018-11-05 13:55:30 -07:00
|
|
|
callback(error, rsp);
|
2018-09-19 11:59:30 -07:00
|
|
|
};
|
|
|
|
|
2018-12-21 16:07:53 -07:00
|
|
|
if (cmdType === 'functional' && entity.functional) {
|
|
|
|
entity.functional(cid, cmd, zclData, cfg, callback_);
|
|
|
|
} else if (cmdType === 'foundation' && entity.foundation) {
|
|
|
|
entity.foundation(cid, cmd, zclData, cfg, callback_);
|
2018-09-19 11:59:30 -07:00
|
|
|
} else {
|
2018-11-05 13:55:30 -07:00
|
|
|
logger.error(`Unknown zigbee publish cmdType ${cmdType}`);
|
2018-04-18 09:25:40 -07:00
|
|
|
}
|
2018-05-30 13:28:08 -07:00
|
|
|
}
|
|
|
|
|
2018-08-28 12:55:00 -07:00
|
|
|
networkScan(callback) {
|
|
|
|
logger.info('Starting network scan...');
|
|
|
|
this.shepherd.lqiScan().then((result) => {
|
|
|
|
logger.info('Network scan completed');
|
|
|
|
callback(result);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
findDevice(deviceID, ep) {
|
2018-05-30 13:28:08 -07:00
|
|
|
// 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;
|