diff --git a/lib/zigbee.js b/lib/zigbee.js index 6f21145ef..3c218dca9 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -292,61 +292,70 @@ class Zigbee { networkScan(callback) { logger.info('Starting network scan...'); + const scanList = new Set(); + const linkMap = []; - Promise.delay = function(t, val) { - return new Promise((resolve) => { - setTimeout(resolve.bind(null, val), t); - }); - }; - - Promise.raceAll = function(promises, timeoutTime, timeoutVal) { - return Promise.all(promises.map((p) => { - return Promise.race([p, Promise.delay(timeoutTime, timeoutVal)]); - })); - }; - - const processResponse = function(parent) { - logger.debug(`Scanning device: '${parent}'`); - return function(data) { - const linkSet = []; - return new Promise((resolve) => { - logger.debug(`Processing scan for: '${parent}'`); - if (data) { - data.forEach(function(devinfo) { - devinfo.parent = parent; - linkSet.push(devinfo); + const processResponse = (error, rsp, parent) => { + if (error) { + logger.warn(`Failed network scan for device: '${parent}' with error: '${error}'`); + } else { + if (scanList.has(parent)) { + // Haven't processed this one yet + if (rsp && rsp.status === 0 && rsp.neighborlqilist) { + logger.debug(`Network scan ok for: '${parent}' with '${rsp.neighborlqilistcount}' neighbors`); + rsp.neighborlqilist.forEach(function(neighbor) { + linkMap.push({ + parent: parent, ieeeAddr: neighbor.extAddr, nwkAddr: neighbor.nwkAddr, + lqi: neighbor.lqi, depth: neighbor.depth}); }); + // Remove from list and if this was the last one return the completed network map + scanList.delete(parent); + if (scanList.size === 0) { + logger.info('Network scan completed'); + linkMap.sort((a, b) => ((a.parent + '|' + a.ieeeAddr) > (b.parent + '|' + b.ieeeAddr)) ? 1 + : (((b.parent + '|' + b.ieeeAddr) > (a.parent + '|' + a.ieeeAddr)) ? -1 : 0)); + logger.debug(`Link map (complete): %j`, linkMap); + callback(linkMap); + } else { + logger.debug(`Still waiting for network scans for devices: '${[...scanList].join(' ')}'`); + } + } else { + logger.warn(`Empty network scan result for: '${parent}'`); } - resolve(linkSet); - logger.debug(`Processed device: '${parent}', linkSet: %j`, linkSet); - }); - }; + } else { + // This ieeeAddr has already been removed due to timeout so don't add to result network map + logger.warn(`Ignoring late network scan result for: '${parent}'`); + } + } }; - const allScans = this.getScanable().map((dev) => { - logger.debug(`Preparing asynch network scan for '${dev.ieeeAddr}'`); - // Delay the start of each device scan by a random time to avoid network congestion - return Promise.delay(Math.random()*3000, this.shepherd.lqi(dev.ieeeAddr)) - .then(processResponse(dev.ieeeAddr)) - .catch(() => { - return new Promise((resolve) => []); - }); - }, this); - logger.debug('All network map promises created'); - // Collect all lqi scan results but timeout after specified miliseconds if any haven't completed - Promise.raceAll(allScans, 8000, []).then((linkSets) => { - // Assemble the individual scan results then sort by ieeeAddr to generate consistent maps - const linkMap = [].concat(...linkSets) - .sort((a, b) => ((a.parent + '|' + a.ieeeAddr) > (b.parent + '|' + b.ieeeAddr)) ? 1 - : (((b.parent + '|' + b.ieeeAddr) > (a.parent + '|' + a.ieeeAddr)) ? -1 : 0)); - logger.info('Network scan completed'); - logger.debug(`Link map: %j`, linkMap); - callback(linkMap); - }) - .catch(function(result) { - logger.info(`Network scan failed: '${result}'`); - callback([]); + // Queue up an lqi scan for coordinator and each router + this.getScanable().forEach((dev) => { + logger.debug(`Queing network scan for device: '${dev.ieeeAddr}'`); + scanList.add(dev.ieeeAddr); + this.queue.push(dev.ieeeAddr, (queueCallback) => { + this.shepherd.controller.request('ZDO', 'mgmtLqiReq', {dstaddr: dev.nwkAddr, startindex: 0}, + (error, rsp, parent) => { + processResponse(error, rsp, dev.ieeeAddr); + queueCallback(error); + }); }); + }); + + // Wait for all device scans before forcing map with whatever results are already in + setTimeout(() => { + if (scanList.size === 0) { + logger.info('Network scan timeout no outstanding requests'); + } else { + logger.warn(`Network scan timeout, skipping outstanding scans for '${[...scanList].join(' ')}'`); + // Clear remaining devices so they don't process when/if they eventually complete + scanList.clear(); + logger.debug(`Link map (timeout): %j`, linkMap); + linkMap.sort((a, b) => ((a.parent + '|' + a.ieeeAddr) > (b.parent + '|' + b.ieeeAddr)) ? 1 + : (((b.parent + '|' + b.ieeeAddr) > (a.parent + '|' + a.ieeeAddr)) ? -1 : 0)); + callback(linkMap); + } + }, scanList.size * 1000); } getEndpoint(ieeeAddr, ep) {