From 3981c1c6e4da33c1d256bb0289ef344df81cc1d5 Mon Sep 17 00:00:00 2001 From: clockbrain <40568549+clockbrain@users.noreply.github.com> Date: Sun, 16 Jun 2019 21:49:15 +1000 Subject: [PATCH] Include network routes on the map (#1641) * Initial inclusion of routes on map * Forgot to include zigbee.js * Hex string for route address * Rebaseline to dev * Cleaner merging of scan arrays * style fixes --- lib/extension/networkMap.js | 4 +- lib/zigbee.js | 114 +++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/lib/extension/networkMap.js b/lib/extension/networkMap.js index 98c05acbe..8cc38bd3a 100644 --- a/lib/extension/networkMap.js +++ b/lib/extension/networkMap.js @@ -89,7 +89,9 @@ class NetworkMap { */ topology.filter((e) => (e.ieeeAddr === device.ieeeAddr) || (e.nwkAddr === device.nwkAddr)).forEach((e) => { const lineStyle = (e.lqi==0) ? `style="dashed", ` : ``; - text += ` "${device.ieeeAddr}" -> "${e.parent}" [`+lineStyle+`label="${e.lqi}"]\n`; + const textRoutes = e.routes.map((r) => `0x${r.toString(16)}`); + const lineLabels = e.lqi + '\\n[' + textRoutes.join(']\\n[') + ']'; + text += ` "${e.parent}" -> "${device.ieeeAddr}" [`+lineStyle+`label="${lineLabels}"]\n`; }); }); diff --git a/lib/zigbee.js b/lib/zigbee.js index 86e767363..771ac3e56 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -292,51 +292,110 @@ class Zigbee { networkScan(callback) { logger.info('Starting network scan...'); - const scanList = new Set(); + const lqiScanList = new Set(); + const rtgScanList = new Set(); const linkMap = []; + const routeMap = []; - const processResponse = (error, rsp, parent) => { + // Gather the lqi and the route info into separate lists and only collate them when done. + const collateMap = () => { + linkMap.sort((a, b) => (a.key > b.key) ? 1 : 0); + logger.debug(`Link map: %j`, linkMap); + logger.debug(`Route map: %j`, routeMap); + // Merge the routes into the linkMap + linkMap.forEach((link) => { + routeMap.filter((e) => e.key === link.key).forEach((e) => { + link.routes.push(e.destAddr); + }); + delete link.key; + }); + logger.debug(`Merged map: %j`, linkMap); + callback(linkMap); + }; + + const processLqiResponse = (error, rsp, parent) => { if (error) { - logger.warn(`Failed network scan for device: '${parent}' with error: '${error}'`); + logger.warn(`Failed network lqi scan for device: '${parent}' with error: '${error}'`); } else { - if (scanList.has(parent)) { + if (lqiScanList.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`); + logger.debug(`lqi scan ok for: '${parent}' with '${rsp.neighborlqilistcount}' neighbors`); rsp.neighborlqilist.forEach(function(neighbor) { + const key = parent + '|' + neighbor.nwkAddr; linkMap.push({ - parent: parent, ieeeAddr: neighbor.extAddr, nwkAddr: neighbor.nwkAddr, - lqi: neighbor.lqi, depth: neighbor.depth}); + key: key, parent: parent, ieeeAddr: neighbor.extAddr, nwkAddr: neighbor.nwkAddr, + lqi: neighbor.lqi, depth: neighbor.depth, routes: []}); }); // Remove from list and if this was the last one return the completed network map - scanList.delete(parent); - if (scanList.size === 0) { + lqiScanList.delete(parent); + if (lqiScanList.size === 0 && rtgScanList === 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); + collateMap(); } else { - logger.debug(`Still waiting for network scans for devices: '${[...scanList].join(' ')}'`); + logger.debug(`Outstanding network lqi scans for devices: '${[...lqiScanList].join(' ')}'`); + logger.debug(`Outstanding network rtg scans for devices: '${[...rtgScanList].join(' ')}'`); } } else { - logger.warn(`Empty network scan result for: '${parent}'`); + logger.warn(`Empty network lqi scan result for: '${parent}'`); } } 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}'`); + logger.warn(`Ignoring late network lqi scan result for: '${parent}'`); } } }; - // Queue up an lqi scan for coordinator and each router + const processRtgResponse = (error, rsp, parent) => { + if (error) { + logger.warn(`Failed network rtg scan for device: '${parent}' with error: '${error}'`); + } else { + if (rtgScanList.has(parent)) { + // Haven't processed this one yet + if (rsp && rsp.status === 0 && rsp.routingtablelist) { + logger.debug(`rtg scan ok for: '${parent}' with '${rsp.routingtablelistcount}' entries`); + rsp.routingtablelist.forEach(function(route) { + if (route.routeStatus === 0) { + const key = parent + '|' + route.nextHopNwkAddr; + routeMap.push({ + key: key, destAddr: route.destNwkAddr}); + } + }); + // Remove from list and if this was the last one return the completed network map + rtgScanList.delete(parent); + if (lqiScanList.size === 0 && rtgScanList.size === 0) { + logger.info('Network scan completed'); + collateMap(); + } else { + logger.debug(`Outstanding network lqi scans for devices: '${[...lqiScanList].join(' ')}'`); + logger.debug(`Outstanding network rtg scans for devices: '${[...rtgScanList].join(' ')}'`); + } + } else { + logger.warn(`Empty network rtg scan result for: '${parent}'`); + } + } else { + // This ieeeAddr has already been removed due to timeout so don't add to result network map + logger.warn(`Ignoring late network rtg scan result for: '${parent}'`); + } + } + }; + + // Queue up an lqi scan and an rtg for coordinator and each router this.getScanable().forEach((dev) => { - logger.debug(`Queing network scan for device: '${dev.ieeeAddr}'`); - scanList.add(dev.ieeeAddr); + logger.debug(`Queing network scans for device: '${dev.ieeeAddr}'`); + lqiScanList.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); + processLqiResponse(error, rsp, dev.ieeeAddr); + queueCallback(error); + }); + }); + rtgScanList.add(dev.ieeeAddr); + this.queue.push(dev.ieeeAddr, (queueCallback) => { + this.shepherd.controller.request('ZDO', 'mgmtRtgReq', {dstaddr: dev.nwkAddr, startindex: 0}, + (error, rsp, parent) => { + processRtgResponse(error, rsp, dev.ieeeAddr); queueCallback(error); }); }); @@ -344,18 +403,17 @@ class Zigbee { // Wait for all device scans before forcing map with whatever results are already in setTimeout(() => { - if (scanList.size === 0) { + if (lqiScanList.size === 0 && rtgScanList.size === 0) { logger.info('Network scan timeout no outstanding requests'); } else { - logger.warn(`Network scan timeout, skipping outstanding scans for '${[...scanList].join(' ')}'`); + logger.warn(`Network scan timeout, skipping outstanding lqi scans for '${[...lqiScanList].join(' ')}'`); + logger.warn(`Network scan timeout, skipping outstanding rtg scans for '${[...rtgScanList].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); + lqiScanList.clear(); + rtgScanList.clear(); + collateMap(); } - }, scanList.size * 1000); + }, lqiScanList.size * 1000); } getEndpoint(ieeeAddr, ep) {