Network map rewrite using queue (#1626)

* Initial rewrite of network map scan using queue

* Rename routerList to scanList

* eslist fixes

* More eslint fixes

* Timeout now dynamic based on number of scanable devices
This commit is contained in:
clockbrain
2019-06-13 21:43:12 +02:00
committed by Koen Kanters
parent 6976f8e70a
commit dd49a76e5c
+58 -49
View File
@@ -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) {