Last active
July 29, 2019 14:27
-
-
Save Koenkk/53e91be26dbc1388d032bcfea17a1ea3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint node: true */ | |
'use strict'; | |
var fs = require('fs'), | |
util = require('util'), | |
EventEmitter = require('events'); | |
var Q = require('q'), | |
_ = require('busyman'), | |
zclId = require('zcl-id'), | |
proving = require('proving'), | |
Objectbox = require('objectbox'), | |
debug = { shepherd: require('debug')('zigbee-shepherd') }; | |
var init = require('./initializers/init_shepherd'), | |
zutils = require('./components/zutils'), | |
Controller = require('./components/controller'), | |
eventHandlers = require('./components/event_handlers'); | |
var Device = require('./model/device'), | |
Coordinator = require('./model/coord'), | |
Group = require('./model/group'), | |
Coordpoint = require('./model/coordpoint'); | |
/*************************************************************************************************/ | |
/*** ZShepherd Class ***/ | |
/*************************************************************************************************/ | |
function ZShepherd(path, opts) { | |
// opts: { sp: {}, net: {}, dbPath: 'xxx' } | |
var self = this, | |
spCfg = {}; | |
EventEmitter.call(this); | |
opts = opts || {}; | |
proving.string(path, 'path should be a string.'); | |
proving.object(opts, 'opts should be an object if gieven.'); | |
spCfg.path = path; | |
spCfg.options = opts.hasOwnProperty('sp') ? opts.sp : { baudrate: 115200, rtscts: true }; | |
/***************************************************/ | |
/*** Protected Members ***/ | |
/***************************************************/ | |
this._startTime = 0; | |
this._enabled = false; | |
this._zApp = []; | |
this._mounting = false; | |
this._mountQueue = []; | |
this.controller = new Controller(this, spCfg); // controller is the main actor | |
this.controller.setNvParams(opts.net); | |
this.af = null; | |
this._dbPath = opts.dbPath; | |
this._coordBackupPath = opts.coordBackupPath; | |
if (!this._dbPath) { // use default | |
this._dbPath = __dirname + '/database/dev.db'; | |
// create default db folder if not there | |
try { | |
fs.statSync(__dirname + '/database'); | |
} catch (e) { | |
fs.mkdirSync(__dirname + '/database'); | |
} | |
} | |
this._devbox = new Objectbox(this._dbPath); | |
this.acceptDevIncoming = function (devInfo, callback) { // Override at will. | |
setImmediate(function () { | |
var accepted = true; | |
callback(null, accepted); | |
}); | |
}; | |
/***************************************************/ | |
/*** Event Handlers (Ind Event Bridges) ***/ | |
/***************************************************/ | |
eventHandlers.attachEventHandlers(this); | |
this.controller.on('permitJoining', function (time) { | |
self.emit('permitJoining', time); | |
}); | |
this.on('_ready', function () { | |
self._startTime = Math.floor(Date.now()/1000); | |
setImmediate(function () { | |
self.emit('ready'); | |
}); | |
}); | |
this.on('ind:incoming', function (dev) { | |
var endpoints = []; | |
_.forEach(dev.epList, function (epId) { | |
endpoints.push(dev.getEndpoint(epId)); | |
}); | |
self.emit('ind', { type: 'devIncoming', endpoints: endpoints, data: dev.getIeeeAddr() }); | |
}); | |
this.on('ind:enddeviceannce', function (dev) { | |
var endpoints = []; | |
_.forEach(dev.epList, function (epId) { | |
endpoints.push(dev.getEndpoint(epId)); | |
}); | |
self.emit('ind', { type: 'endDeviceAnnce', endpoints: endpoints, data: dev.getIeeeAddr() }); | |
}); | |
this.on('ind:interview', function (dev, status) { | |
self.emit('ind', { type: 'devInterview', status: status, data: dev }); | |
}); | |
this.on('ind:leaving', function (epList, ieeeAddr) { | |
self.emit('ind', { type: 'devLeaving', endpoints: epList, data: ieeeAddr }); | |
}); | |
this.on('ind:changed', function (ep, notifData) { | |
self.emit('ind', { type: 'devChange', endpoints: [ ep ], data: notifData }); | |
}); | |
this.on('ind:cmd', function (ep, cId, payload, cmdId, msg) { | |
const cIdString = zclId.cluster(cId); | |
const type = `cmd${cmdId.charAt(0).toUpperCase() + cmdId.substr(1)}`; | |
const notifData = {}; | |
notifData.cid = cIdString ? cIdString.key : cId; | |
notifData.data = payload; | |
self.emit('ind', { type: type, endpoints: [ ep ], data: notifData, linkquality: msg.linkquality, groupid: msg.groupid }); | |
}); | |
this.on('ind:statusChange', function (ep, cId, payload, msg) { | |
var cIdString = zclId.cluster(cId), | |
notifData = { | |
cid: '', | |
zoneStatus: null | |
}; | |
cIdString = cIdString ? cIdString.key : cId; | |
notifData.cid = cIdString; | |
notifData.zoneStatus = payload.zonestatus; | |
self.emit('ind', { type: 'statusChange', endpoints: [ ep ], data: notifData, linkquality: msg.linkquality }); | |
}); | |
this.on('ind:reported', function (ep, cId, attrs, msg) { | |
var cIdString = zclId.cluster(cId), | |
notifData = { | |
cid: '', | |
data: {} | |
}; | |
console.log("=+=+=+=", "reported", cId, attrs, msg); | |
self._updateFinalizer(ep, cId, attrs, true); | |
cIdString = cIdString ? cIdString.key : cId; | |
notifData.cid = cIdString; | |
_.forEach(attrs, function (rec) { // { attrId, dataType, attrData } | |
var attrIdString = zclId.attr(cIdString, rec.attrId); | |
attrIdString = attrIdString ? attrIdString.key : rec.attrId; | |
notifData.data[attrIdString] = rec.attrData; | |
if (attrIdString === 'modelId' && !ep.device.modelId) { | |
/** | |
* Xiaomi devices report it's modelId through a genBasic message. | |
* Set this as the modelId when the device doesn't have one yet. | |
*/ | |
ep.device.update({modelId: rec.attrData}); | |
Q.ninvoke(self._devbox, 'sync', ep.device._getId()); | |
} | |
}); | |
self.emit('ind', { type: 'attReport', endpoints: [ ep ], data: notifData, linkquality: msg.linkquality, groupid: msg.groupid }); | |
}); | |
this.on('ind:readRsp', function (ep, cId, attrs, msg) { | |
var cIdString = zclId.cluster(cId), | |
notifData = { | |
cid: '', | |
data: {} | |
}; | |
console.log("=+=+=+=", "readrsp", cId, attrs, msg); | |
self._updateFinalizer(ep, cId, attrs, true); | |
cIdString = cIdString ? cIdString.key : cId; | |
notifData.cid = cIdString; | |
_.forEach(attrs, function (rec) { // { attrId, dataType, attrData } | |
var attrIdString = zclId.attr(cIdString, rec.attrId); | |
attrIdString = attrIdString ? attrIdString.key : rec.attrId; | |
const skip = attrIdString === 'modelId' && rec.attrData.replace(/\0/g, '') === ''; | |
if (!skip) { | |
notifData.data[attrIdString] = rec.attrData; | |
} else { | |
console.log("------------- skipped modelid") | |
} | |
}); | |
self.emit('ind', { type: 'readRsp', endpoints: [ ep ], data: notifData, linkquality: msg.linkquality }); | |
}); | |
this.on('ind:status', function (dev, status) { | |
var endpoints = []; | |
_.forEach(dev.epList, function (epId) { | |
endpoints.push(dev.getEndpoint(epId)); | |
}); | |
self.emit('ind', { type: 'devStatus', endpoints: endpoints, data: status }); | |
}); | |
} | |
util.inherits(ZShepherd, EventEmitter); | |
/*************************************************************************************************/ | |
/*** Public Methods ***/ | |
/*************************************************************************************************/ | |
ZShepherd.prototype.start = function (callback) { | |
var self = this; | |
return init.setupShepherd(this).then(function () { | |
self._enabled = true; // shepherd is enabled | |
self.emit('_ready'); // if all done, shepherd fires '_ready' event for inner use | |
debug.shepherd('zigbee-shepherd is _ready and _enabled'); | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype.stop = function (callback) { | |
var self = this, | |
devbox = this._devbox; | |
debug.shepherd('zigbee-shepherd is stopping.'); | |
return Q.fcall(function () { | |
if (self._enabled) { | |
self.permitJoin(0x00, 'all'); | |
_.forEach(devbox.exportAllIds(), function (id) { | |
devbox.removeElement(id); | |
}); | |
return self.controller.close(); | |
} | |
}).then(function () { | |
self._enabled = false; | |
self._zApp = null; | |
self._zApp = []; | |
debug.shepherd('zigbee-shepherd is stopped.'); | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype.reset = function (mode, callback) { | |
var self = this, | |
devbox = this._devbox, | |
removeDevs = []; | |
proving.stringOrNumber(mode, 'mode should be a number or a string.'); | |
if (mode === 'hard' || mode === 0) { | |
// clear database | |
if (self._devbox) { | |
_.forEach(devbox.exportAllIds(), function (id) { | |
removeDevs.push(Q.ninvoke(devbox, 'remove', id)); | |
}); | |
Q.all(removeDevs).then(function () { | |
if (devbox.isEmpty()) | |
debug.shepherd('Database cleared.'); | |
else | |
debug.shepherd('Database not cleared.'); | |
}).fail(function (err) { | |
debug.shepherd(err); | |
}).done(); | |
} else { | |
devbox = new Objectbox(this._dbPath); | |
} | |
} | |
return this.controller.reset(mode, callback); | |
}; | |
ZShepherd.prototype.permitJoin = function (time, type, callback) { | |
if (_.isFunction(type) && !_.isFunction(callback)) { | |
callback = type; | |
type = 'all'; | |
} else { | |
type = type || 'all'; | |
} | |
if (!this._enabled) | |
return Q.reject(new Error('Shepherd is not enabled.')).nodeify(callback); | |
else | |
return this.controller.permitJoin(time, type, callback); | |
}; | |
ZShepherd.prototype.backupCoordinator = function (callback) { | |
if (!this._coordBackupPath) | |
return Q.reject(new Error('coordBackupPath not set')).nodeify(callback); | |
else if (!this._enabled) | |
return Q.reject(new Error('Shepherd is not enabled.')).nodeify(callback); | |
else | |
return this.controller.backupCoordinator(this._coordBackupPath, callback); | |
} | |
ZShepherd.prototype.info = function () { | |
var net = this.controller.getNetInfo(); | |
var firmware = this.controller.getFirmwareInfo(); | |
return { | |
enabled: this._enabled, | |
net: { | |
state: net.state, | |
channel: net.channel, | |
panId: net.panId, | |
extPanId: net.extPanId, | |
ieeeAddr: net.ieeeAddr, | |
nwkAddr: net.nwkAddr, | |
}, | |
firmware: firmware, | |
startTime: this._startTime, | |
joinTimeLeft: net.joinTimeLeft | |
}; | |
}; | |
ZShepherd.prototype.mount = function (zApp, callback) { | |
var self = this, | |
deferred = (callback && Q.isPromise(callback.promise)) ? callback : Q.defer(), | |
coord = this.controller._coord, | |
mountId, | |
loEp; | |
if (zApp.constructor.name !== 'Zive') | |
throw new TypeError('zApp should be an instance of Zive class.'); | |
if (this._mounting) { | |
this._mountQueue.push(function () { | |
self.mount(zApp, deferred); | |
}); | |
return deferred.promise.nodeify(callback); | |
} | |
this._mounting = true; | |
Q.fcall(function () { | |
_.forEach(self._zApp, function (app) { | |
if (app === zApp) | |
throw new Error('zApp already exists.'); | |
}); | |
self._zApp.push(zApp); | |
}).then(function () { | |
if (coord) { | |
mountId = Math.max.apply(null, coord.epList); | |
zApp._simpleDesc.epId = mountId > 10 ? mountId + 1 : 11; // epId 1-10 are reserved for delegator | |
loEp = new Coordpoint(coord, zApp._simpleDesc); | |
loEp.clusters = zApp.clusters; | |
coord.endpoints[loEp.getEpId()] = loEp; | |
zApp._endpoint = loEp; | |
} else { | |
throw new Error('Coordinator has not been initialized yet.'); | |
} | |
}).then(function () { | |
return self.controller.registerEp(loEp).then(function () { | |
debug.shepherd('Register zApp, epId: %s, profId: %s ', loEp.getEpId(), loEp.getProfId()); | |
}); | |
}).then(function () { | |
return self.controller.query.coordInfo().then(function (coordInfo) { | |
coord.update(coordInfo); | |
return Q.ninvoke(self._devbox, 'sync', coord._getId()); | |
}); | |
}).then(function () { | |
self._attachZclMethods(loEp); | |
self._attachZclMethods(zApp); | |
loEp.onZclFoundation = function (msg, remoteEp) { | |
setImmediate(function () { | |
return zApp.foundationHandler(msg, remoteEp); | |
}); | |
}; | |
loEp.onZclFunctional = function (msg, remoteEp) { | |
setImmediate(function () { | |
return zApp.functionalHandler(msg, remoteEp); | |
}); | |
}; | |
deferred.resolve(loEp.getEpId()); | |
}).fail(function (err) { | |
deferred.reject(err); | |
}).done(function () { | |
self._mounting = false; | |
if (self._mountQueue.length) | |
process.nextTick(function () { | |
self._mountQueue.shift()(); | |
}); | |
}); | |
if (!(callback && Q.isPromise(callback.promise))) | |
return deferred.promise.nodeify(callback); | |
}; | |
ZShepherd.prototype.list = function (ieeeAddrs) { | |
var self = this, | |
foundDevs; | |
if (_.isString(ieeeAddrs)) | |
ieeeAddrs = [ ieeeAddrs ]; | |
else if (!_.isUndefined(ieeeAddrs) && !_.isArray(ieeeAddrs)) | |
throw new TypeError('ieeeAddrs should be a string or an array of strings if given.'); | |
else if (!ieeeAddrs) | |
ieeeAddrs = _.map(this._devbox.exportAllObjs(), function (dev) { | |
return dev.getIeeeAddr(); // list all | |
}); | |
foundDevs = _.map(ieeeAddrs, function (ieeeAddr) { | |
proving.string(ieeeAddr, 'ieeeAddr should be a string.'); | |
var devInfo, | |
found = self._findDevByAddr(ieeeAddr); | |
if (found) | |
devInfo = _.omit(found.dump(), [ 'id', 'endpoints' ]); | |
return devInfo; // will push undefined to foundDevs array if not found | |
}); | |
return foundDevs; | |
}; | |
ZShepherd.prototype.getGroup = function (groupID) { | |
proving.number(groupID, 'groupID should be a number.'); | |
const group = new Group(groupID); | |
this._attachZclMethods(group); | |
return group; | |
}; | |
ZShepherd.prototype.find = function (addr, epId) { | |
proving.number(epId, 'epId should be a number.'); | |
var dev = this._findDevByAddr(addr); | |
return dev ? dev.getEndpoint(epId) : undefined; | |
}; | |
ZShepherd.prototype.lqi = function (ieeeAddr, callback) { | |
proving.string(ieeeAddr, 'ieeeAddr should be a string.'); | |
var self = this, | |
dev = this._findDevByAddr(ieeeAddr); | |
return Q.fcall(function () { | |
if (dev) | |
return self.controller.request('ZDO', 'mgmtLqiReq', { dstaddr: dev.getNwkAddr(), startindex: 0 }); | |
else | |
return Q.reject(new Error('device is not found.')); | |
}).then(function (rsp) { // { srcaddr, status, neighbortableentries, startindex, neighborlqilistcount, neighborlqilist } | |
if (rsp.status === 0) // success | |
return _.map(rsp.neighborlqilist, function (neighbor) { | |
return { ieeeAddr: neighbor.extAddr, nwkAddr: neighbor.nwkAddr, lqi: neighbor.lqi }; | |
}); | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype.remove = function (ieeeAddr, cfg, callback) { | |
proving.string(ieeeAddr, 'ieeeAddr should be a string.'); | |
var dev = this._findDevByAddr(ieeeAddr); | |
if (_.isFunction(cfg) && !_.isFunction(callback)) { | |
callback = cfg; | |
cfg = {}; | |
} else { | |
cfg = cfg || {}; | |
} | |
if (!dev) | |
return Q.reject(new Error('device is not found.')).nodeify(callback); | |
else | |
return this.controller.remove(dev, cfg, callback); | |
}; | |
ZShepherd.prototype.lqiScan = function (ieeeAddr) { | |
var info = this.info(); | |
var self = this; | |
const noDuplicate = {}; | |
const processResponse = function(parent){ | |
return function(data){ | |
var chain = Q(); | |
data.forEach(function (devinfo) { | |
const ieeeAddr = devinfo.ieeeAddr; | |
if (ieeeAddr == "0x0000000000000000") return; | |
let dev = self._findDevByAddr(ieeeAddr); | |
devinfo.parent = parent; | |
devinfo.status = dev ? dev.status : "offline"; | |
const dedupKey = parent + '|' + ieeeAddr; | |
if (dev && dev.type == "Router" && !noDuplicate[dedupKey]) { | |
chain = chain.then(function () { | |
return self.lqi(ieeeAddr).then(processResponse(ieeeAddr)); | |
}); | |
} | |
noDuplicate[dedupKey] = devinfo; | |
}); | |
return chain; | |
} | |
} | |
if(!ieeeAddr){ | |
ieeeAddr = info.net.ieeeAddr; | |
} | |
return self.lqi(ieeeAddr) | |
.timeout(5000) | |
.then(processResponse(ieeeAddr)) | |
.then(function(){ | |
return Object.values(noDuplicate); | |
}) | |
.catch(function(){ | |
return Object.values(noDuplicate); | |
}); | |
}; | |
/*************************************************************************************************/ | |
/*** Protected Methods ***/ | |
/*************************************************************************************************/ | |
ZShepherd.prototype._findDevByAddr = function (addr) { | |
// addr: ieeeAddr(String) or nwkAddr(Number) | |
proving.stringOrNumber(addr, 'addr should be a number or a string.'); | |
return this._devbox.find(function (dev) { | |
return _.isString(addr) ? dev.getIeeeAddr() === addr : dev.getNwkAddr() === addr; | |
}); | |
}; | |
ZShepherd.prototype._registerDev = function (dev, callback) { | |
var devbox = this._devbox, | |
oldDev; | |
if (!(dev instanceof Device) && !(dev instanceof Coordinator)) | |
throw new TypeError('dev should be an instance of Device class.'); | |
oldDev = _.isNil(dev._getId()) ? undefined : devbox.get(dev._getId()); | |
return Q.fcall(function () { | |
if (oldDev) { | |
throw new Error('dev exists, unregister it first.'); | |
} else if (dev._recovered) { | |
return Q.ninvoke(devbox, 'set', dev._getId(), dev).then(function (id) { | |
dev._recovered = false; | |
delete dev._recovered; | |
return id; | |
}); | |
} else { | |
dev.update({ joinTime: Math.floor(Date.now()/1000) }); | |
return Q.ninvoke(devbox, 'add', dev).then(function (id) { | |
dev._setId(id); | |
return id; | |
}); | |
} | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype._unregisterDev = function (dev, callback) { | |
return Q.ninvoke(this._devbox, 'remove', dev._getId()).nodeify(callback); | |
}; | |
ZShepherd.prototype._attachZclMethods = function (ep) { | |
var self = this; | |
if (ep.constructor.name === 'Zive') { | |
var zApp = ep; | |
zApp.foundation = function (dstAddr, dstEpId, cId, cmd, zclData, cfg, callback) { | |
var dstEp = self.find(dstAddr, dstEpId); | |
if (typeof cfg === 'function') { | |
callback = cfg; | |
cfg = {}; | |
} | |
if (!dstEp) | |
return Q.reject(new Error('dstEp is not found.')).nodeify(callback); | |
else | |
return self._foundation(zApp._endpoint, dstEp, cId, cmd, zclData, cfg, callback); | |
}; | |
zApp.functional = function (dstAddr, dstEpId, cId, cmd, zclData, cfg, callback) { | |
var dstEp = self.find(dstAddr, dstEpId); | |
if (typeof cfg === 'function') { | |
callback = cfg; | |
cfg = {}; | |
} | |
if (!dstEp) | |
return Q.reject(new Error('dstEp is not found.')).nodeify(callback); | |
else | |
return self._functional(zApp._endpoint, dstEp, cId, cmd, zclData, cfg, callback); | |
}; | |
} else if (ep instanceof Group) { | |
ep.functional = function (cId, cmd, zclData, cfg, callback) { | |
return self._functional(ep, ep, cId, cmd, zclData, cfg, callback); | |
}; | |
} else { | |
ep.foundation = function (cId, cmd, zclData, cfg, callback) { | |
return self._foundation(ep, ep, cId, cmd, zclData, cfg, callback); | |
}; | |
ep.functional = function (cId, cmd, zclData, cfg, callback) { | |
return self._functional(ep, ep, cId, cmd, zclData, cfg, callback); | |
}; | |
ep.bind = function (cId, dstEpOrGrpId, callback) { | |
return self.controller.bind(ep, cId, dstEpOrGrpId, callback); | |
}; | |
ep.unbind = function (cId, dstEpOrGrpId, callback) { | |
return self.controller.unbind(ep, cId, dstEpOrGrpId, callback); | |
}; | |
ep.read = function (cId, attrId, callback) { | |
var deferred = Q.defer(), | |
attr = zclId.attr(cId, attrId); | |
attr = attr ? attr.value : attrId; | |
self._foundation(ep, ep, cId, 'read', [{ attrId: attr }]).then(function (readStatusRecsRsp) { | |
var rec = readStatusRecsRsp[0]; | |
if (rec.status === 0) | |
deferred.resolve(rec.attrData); | |
else | |
deferred.reject(new Error('request unsuccess: ' + rec.status)); | |
}).catch(function(err) { | |
deferred.reject(err); | |
}); | |
return deferred.promise.nodeify(callback); | |
}; | |
ep.write = function (cId, attrId, data, callback) { | |
var deferred = Q.defer(), | |
attr = zclId.attr(cId, attrId), | |
attrType = zclId.attrType(cId, attrId).value; | |
self._foundation(ep, ep, cId, 'write', [{ attrId: attr.value, dataType: attrType, attrData: data }]).then(function (writeStatusRecsRsp) { | |
var rec = writeStatusRecsRsp[0]; | |
if (rec.status === 0) | |
deferred.resolve(data); | |
else | |
deferred.reject(new Error('request unsuccess: ' + rec.status)); | |
}).catch(function(err) { | |
deferred.reject(err); | |
}); | |
return deferred.promise.nodeify(callback); | |
}; | |
ep.report = function (cId, attrId, minInt, maxInt, repChange, callback) { | |
var deferred = Q.defer(), | |
coord = self.controller._coord, | |
dlgEp = coord.getDelegator(ep.getProfId()), | |
cfgRpt = true, | |
cfgRptRec, | |
attrIdVal, | |
attrTypeVal; | |
if (arguments.length === 1) { | |
cfgRpt = false; | |
} else if (arguments.length === 2) { | |
callback = attrId; | |
cfgRpt = false; | |
} else if (arguments.length === 5 && _.isFunction(repChange)) { | |
callback = repChange; | |
} | |
if (cfgRpt) { | |
attrIdVal = zclId.attr(cId, attrId); | |
cfgRptRec = { | |
direction : 0, | |
attrId: attrIdVal ? attrIdVal.value : attrId, | |
dataType : zclId.attrType(cId, attrId).value, | |
minRepIntval : minInt, | |
maxRepIntval : maxInt, | |
repChange: repChange | |
}; | |
} | |
Q.fcall(function () { | |
if (dlgEp) { | |
return ep.bind(cId, dlgEp).then(function () { | |
if (cfgRpt) | |
return ep.foundation(cId, 'configReport', [ cfgRptRec ]).then(function (rsp) { | |
var status = rsp[0].status; | |
if (status !== 0) | |
deferred.reject(zclId.status(status).key); | |
}); | |
}); | |
} else { | |
return Q.reject(new Error('Profile: ' + ep.getProfId() + ' is not supported.')); | |
} | |
}).then(function () { | |
deferred.resolve(); | |
}).fail(function (err) { | |
deferred.reject(err); | |
}).done(); | |
return deferred.promise.nodeify(callback); | |
}; | |
} | |
}; | |
ZShepherd.prototype._foundation = function (srcEp, dstEp, cId, cmd, zclData, cfg, callback) { | |
var self = this; | |
if (_.isFunction(cfg) && !_.isFunction(callback)) { | |
callback = cfg; | |
cfg = {}; | |
} else { | |
cfg = cfg || {}; | |
} | |
return this.af.zclFoundation(srcEp, dstEp, cId, cmd, zclData, cfg).then(function (msg) { | |
var cmdString = zclId.foundation(cmd); | |
cmdString = cmdString ? cmdString.key : cmd; | |
console.log("=+=+=+=", "foundationclaled", cId, cmd, zclData); | |
if (cmdString === 'read') | |
self._updateFinalizer(dstEp, cId, msg.payload); | |
else if (cmdString === 'write' || cmdString === 'writeUndiv' || cmdString === 'writeNoRsp') | |
self._updateFinalizer(dstEp, cId); | |
return msg.payload; | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype._functional = function (srcEp, dstEp, cId, cmd, zclData, cfg, callback) { | |
var self = this; | |
if (_.isFunction(cfg) && !_.isFunction(callback)) { | |
callback = cfg; | |
cfg = {}; | |
} else { | |
cfg = cfg || {}; | |
} | |
return this.af.zclFunctional(srcEp, dstEp, cId, cmd, zclData, cfg).then(function (msg) { | |
console.log("=+=+=+=", "zclfunctional", cId, cmd, zclData); | |
self._updateFinalizer(dstEp, cId); | |
return msg.payload; | |
}).nodeify(callback); | |
}; | |
ZShepherd.prototype._updateFinalizer = function (ep, cId, attrs, reported) { | |
// Some eps don't have clusters, e.g. a Group | |
console.log('=== updatefinalizer', ep.device.ieeeAddr, cId, attrs, reported); | |
if (!ep.getClusters) { | |
return; | |
} | |
var self = this, | |
cIdString = zclId.cluster(cId), | |
clusters = ep.getClusters().dumpSync(); | |
cIdString = cIdString ? cIdString.key : cId; | |
Q.fcall(function () { | |
if (attrs) { | |
var newAttrs = {}; | |
_.forEach(attrs, function (rec) { // { attrId, status, dataType, attrData } | |
console.log("=== rec", rec); | |
var attrIdString = zclId.attr(cId, rec.attrId); | |
attrIdString = attrIdString ? attrIdString.key : rec.attrId; | |
const skip = attrIdString === 'modelId' && rec.attrData.replace(/\0/g, '') === ''; | |
if (!skip) { | |
if (reported) | |
newAttrs[attrIdString] = rec.attrData; | |
else | |
newAttrs[attrIdString] = (rec.status === 0) ? rec.attrData : null; | |
} else { | |
console.log('==== skip', rec); | |
} | |
}); | |
return newAttrs; | |
} else { | |
return self.af.zclClusterAttrsReq(ep, cId); | |
} | |
}).then(function (newAttrs) { | |
var oldAttrs = clusters[cIdString].attrs, | |
diff = zutils.objectDiff(oldAttrs, newAttrs); | |
if (!_.isEmpty(diff)) { | |
_.forEach(diff, function (val, attrId) { | |
ep.getClusters().set(cIdString, 'attrs', attrId, val); | |
}); | |
self.emit('ind:changed', ep, { cid: cIdString, data: diff }); | |
} | |
}).fail(function () { | |
return; | |
}).done(); | |
}; | |
module.exports = ZShepherd; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment