mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 05:24:29 +00:00
Network map fixes (#1676)
* Include scan failed note in network map * Include relationship in map structure * Clearer source and destination node naming * eslint fixes * More eslint fixes * Removed empty brackets for no routes * Reposition short address in device node * eslint fixes
This commit is contained in:
+26
-19
@@ -58,17 +58,21 @@ class NetworkMap {
|
||||
let text = 'digraph G {\nnode[shape=record];\n';
|
||||
let devStyle = '';
|
||||
|
||||
zigbee.getDevices().forEach((device) => {
|
||||
topology.nodes.forEach((device) => {
|
||||
const labels = [];
|
||||
const friendlyDevice = settings.getDevice(device.ieeeAddr);
|
||||
const friendlyName = friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr;
|
||||
|
||||
// Add friendly name
|
||||
labels.push(`${friendlyName}:${`0x${device.nwkAddr.toString(16)}`}`);
|
||||
labels.push(`${device.friendlyName}`);
|
||||
|
||||
// Add the device type
|
||||
const deviceType = utils.correctDeviceType(device);
|
||||
labels.push(deviceType);
|
||||
// Add the device short network address and scan note (if any)
|
||||
let scanNote = '';
|
||||
if (device.scanfailed.includes('lqi')) {
|
||||
scanNote += ' no lqi';
|
||||
}
|
||||
if (device.scanfailed.includes('rtg')) {
|
||||
scanNote += ' no routes';
|
||||
}
|
||||
labels.push(`0x${device.nwkAddr.toString(16)} ${scanNote}`);
|
||||
|
||||
// Add the device model
|
||||
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
||||
@@ -96,10 +100,10 @@ class NetworkMap {
|
||||
labels.push(`${device.status} (${lastSeen})`);
|
||||
|
||||
// Shape the record according to device type
|
||||
if (deviceType == 'Coordinator') {
|
||||
if (device.type == 'Coordinator') {
|
||||
devStyle = `style="bold, filled", fillcolor="${colors.fill.coordinator}", ` +
|
||||
`fontcolor="${colors.font.coordinator}"`;
|
||||
} else if (deviceType == 'Router') {
|
||||
} else if (device.type == 'Router') {
|
||||
devStyle = `style="rounded, filled", fillcolor="${colors.fill.router}", ` +
|
||||
`fontcolor="${colors.font.router}"`;
|
||||
} else {
|
||||
@@ -111,19 +115,22 @@ class NetworkMap {
|
||||
text += ` "${device.ieeeAddr}" [`+devStyle+`, label="{${labels.join('|')}}"];\n`;
|
||||
|
||||
/**
|
||||
* Add an edge between the device and its parent to the graph
|
||||
* Add an edge between the device and its child to the graph
|
||||
* NOTE: There are situations where a device is NOT in the topology, this can be e.g.
|
||||
* due to not responded to the lqi scan. In that case we do not add an edge for this device.
|
||||
*/
|
||||
topology.filter((e) => (e.ieeeAddr === device.ieeeAddr) || (e.nwkAddr === device.nwkAddr)).forEach((e) => {
|
||||
const lineStyle = (device.type=='EndDevice') ? 'style="dashed", '
|
||||
: (!e.routes.length) ? 'style="dotted", ' : '';
|
||||
const lineWeight = (!e.routes.length) ? `weight=0, color="${colors.line.inactive}", `
|
||||
: `weight=1, color="${colors.line.active}", `;
|
||||
const textRoutes = e.routes.map((r) => `0x${r.toString(16)}`);
|
||||
const lineLabels = `label="${e.lqi}\\n[${textRoutes.join(']\\n[')}]"`;
|
||||
text += ` "${e.parent}" -> "${device.ieeeAddr}" [${lineStyle}${lineWeight}${lineLabels}]\n`;
|
||||
});
|
||||
topology.links.filter((e) => (e.sourceIeeeAddr === device.ieeeAddr) || (e.SourceNwkAddr === device.nwkAddr))
|
||||
.forEach((e) => {
|
||||
const lineStyle = (device.type=='EndDevice') ? 'style="dashed", '
|
||||
: (!e.routes.length) ? 'style="dotted", ' : '';
|
||||
const lineWeight = (!e.routes.length) ? `weight=0, color="${colors.line.inactive}", `
|
||||
: `weight=1, color="${colors.line.active}", `;
|
||||
const textRoutes = e.routes.map((r) => `0x${r.toString(16)}`);
|
||||
const lineLabels = (!e.routes.length) ? `label="${e.lqi}"`
|
||||
: `label="${e.lqi}\\n[${textRoutes.join(']\\n[')}]"`;
|
||||
text += ` "${device.ieeeAddr}" -> "${e.targetIeeeAddr}"`;
|
||||
text += ` [${lineStyle}${lineWeight}${lineLabels}]\n`;
|
||||
});
|
||||
});
|
||||
|
||||
text += '}';
|
||||
|
||||
+59
-35
@@ -317,36 +317,63 @@ class Zigbee {
|
||||
|
||||
// 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);
|
||||
linkMap.sort();
|
||||
logger.debug(`Link map: %j`, linkMap);
|
||||
logger.debug(`Route map: %j`, routeMap);
|
||||
// Merge the routes into the linkMap
|
||||
// Merge the routes into the linkMap by matching on 'source|target' link short addresses
|
||||
linkMap.forEach((link) => {
|
||||
routeMap.filter((e) => e.key === link.key).forEach((e) => {
|
||||
link.routes.push(e.destAddr);
|
||||
});
|
||||
link.routes.sort(function(a, b) {
|
||||
return a-b;
|
||||
});
|
||||
delete link.key;
|
||||
});
|
||||
logger.debug(`Merged map: %j`, linkMap);
|
||||
callback(linkMap);
|
||||
const networkMap = {nodes: [], links: linkMap};
|
||||
this.getDevices().forEach((device) => {
|
||||
const friendlyDevice = settings.getDevice(device.ieeeAddr);
|
||||
const friendlyName = friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr;
|
||||
const deviceType = utils.correctDeviceType(device);
|
||||
const scanfailed = [];
|
||||
if (lqiScanList.has(device.ieeeAddr)) {
|
||||
scanfailed.push('lqi');
|
||||
}
|
||||
if (rtgScanList.has(device.ieeeAddr)) {
|
||||
scanfailed.push('rtg');
|
||||
}
|
||||
networkMap.nodes.push({ieeeAddr: device.ieeeAddr, friendlyName: friendlyName, type: deviceType,
|
||||
nwkAddr: device.nwkAddr, manufName: device.manufName, modelId: device.modelId,
|
||||
status: device.status, scanfailed: scanfailed});
|
||||
});
|
||||
// Clear remaining devices so they don't process when/if they eventually complete
|
||||
lqiScanList.clear();
|
||||
rtgScanList.clear();
|
||||
logger.debug(`Merged map: %j`, networkMap);
|
||||
callback(networkMap);
|
||||
};
|
||||
|
||||
const processLqiResponse = (error, rsp, parent) => {
|
||||
const processLqiResponse = (error, rsp, targetIeeeAddr, targetNwkAddr) => {
|
||||
if (error) {
|
||||
logger.warn(`Failed network lqi scan for device: '${parent}' with error: '${error}'`);
|
||||
logger.warn(`Failed network lqi scan for device: '${targetIeeeAddr}' with error: '${error}'`);
|
||||
} else {
|
||||
if (lqiScanList.has(parent)) {
|
||||
if (lqiScanList.has(targetIeeeAddr)) {
|
||||
// Haven't processed this one yet
|
||||
if (rsp && rsp.status === 0 && rsp.neighborlqilist) {
|
||||
logger.debug(`lqi scan ok for: '${parent}' with '${rsp.neighborlqilistcount}' neighbors`);
|
||||
logger.debug(`lqi scan: '${targetIeeeAddr}' with '${rsp.neighborlqilistcount}' neighbors`);
|
||||
rsp.neighborlqilist.forEach(function(neighbor) {
|
||||
const key = parent + '|' + neighbor.nwkAddr;
|
||||
linkMap.push({
|
||||
key: key, parent: parent, ieeeAddr: neighbor.extAddr, nwkAddr: neighbor.nwkAddr,
|
||||
lqi: neighbor.lqi, depth: neighbor.depth, routes: []});
|
||||
// only include active relationships
|
||||
if (neighbor.relationship <= 3) {
|
||||
// lqi is measured at receiver so link is from neighbor (source) to scanned router
|
||||
const key = neighbor.nwkAddr + '|' + targetNwkAddr;
|
||||
linkMap.push({
|
||||
key: key, sourceIeeeAddr: neighbor.extAddr, targetIeeeAddr: targetIeeeAddr,
|
||||
sourceNwkAddr: neighbor.nwkAddr, lqi: neighbor.lqi, depth: neighbor.depth,
|
||||
relationship: neighbor.relationship, routes: []});
|
||||
}
|
||||
});
|
||||
// Remove from list and if this was the last one return the completed network map
|
||||
lqiScanList.delete(parent);
|
||||
// Remove from scan list and if both lists are done return the completed network map
|
||||
lqiScanList.delete(targetIeeeAddr);
|
||||
if (lqiScanList.size === 0 && rtgScanList === 0) {
|
||||
logger.info('Network scan completed');
|
||||
collateMap();
|
||||
@@ -355,32 +382,32 @@ class Zigbee {
|
||||
logger.debug(`Outstanding network rtg scans for devices: '${[...rtgScanList].join(' ')}'`);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Empty network lqi scan result for: '${parent}'`);
|
||||
logger.warn(`Empty network lqi scan result for: '${targetIeeeAddr}'`);
|
||||
}
|
||||
} else {
|
||||
// This ieeeAddr has already been removed due to timeout so don't add to result network map
|
||||
logger.warn(`Ignoring late network lqi scan result for: '${parent}'`);
|
||||
// This target has already had timeout so don't add to result network map
|
||||
logger.warn(`Ignoring late network lqi scan result for: '${targetIeeeAddr}'`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processRtgResponse = (error, rsp, parent) => {
|
||||
const processRtgResponse = (error, rsp, sourceIeeeAddr, sourceNwkAddr) => {
|
||||
if (error) {
|
||||
logger.warn(`Failed network rtg scan for device: '${parent}' with error: '${error}'`);
|
||||
logger.warn(`Failed network rtg scan for device: '${sourceIeeeAddr}' with error: '${error}'`);
|
||||
} else {
|
||||
if (rtgScanList.has(parent)) {
|
||||
if (rtgScanList.has(sourceIeeeAddr)) {
|
||||
// Haven't processed this one yet
|
||||
if (rsp && rsp.status === 0 && rsp.routingtablelist) {
|
||||
logger.debug(`rtg scan ok for: '${parent}' with '${rsp.routingtablelistcount}' entries`);
|
||||
logger.debug(`rtg scan: '${sourceIeeeAddr}' with '${rsp.routingtablelistcount}' entries`);
|
||||
rsp.routingtablelist.forEach(function(route) {
|
||||
if (route.routeStatus === 0) {
|
||||
const key = parent + '|' + route.nextHopNwkAddr;
|
||||
const key = sourceNwkAddr + '|' + 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);
|
||||
// Remove from scan list and if both lists are done return the completed network map
|
||||
rtgScanList.delete(sourceIeeeAddr);
|
||||
if (lqiScanList.size === 0 && rtgScanList.size === 0) {
|
||||
logger.info('Network scan completed');
|
||||
collateMap();
|
||||
@@ -389,31 +416,31 @@ class Zigbee {
|
||||
logger.debug(`Outstanding network rtg scans for devices: '${[...rtgScanList].join(' ')}'`);
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Empty network rtg scan result for: '${parent}'`);
|
||||
logger.warn(`Empty network rtg scan result for: '${sourceIeeeAddr}'`);
|
||||
}
|
||||
} 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}'`);
|
||||
// This source has already had timeout so don't add to result network map
|
||||
logger.warn(`Ignoring late network rtg scan result for: '${sourceIeeeAddr}'`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Queue up an lqi scan and an rtg for coordinator and each router
|
||||
// Queue up an lqi scan and an rtg scan for coordinator and each router
|
||||
this.getScanable().forEach((dev) => {
|
||||
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) => {
|
||||
processLqiResponse(error, rsp, dev.ieeeAddr);
|
||||
(error, rsp, ieeeAddr, nwkAddr) => {
|
||||
processLqiResponse(error, rsp, dev.ieeeAddr, dev.nwkAddr);
|
||||
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);
|
||||
(error, rsp, ieeeAddr, nwkAddr) => {
|
||||
processRtgResponse(error, rsp, dev.ieeeAddr, dev.nwkAddr);
|
||||
queueCallback(error);
|
||||
});
|
||||
});
|
||||
@@ -426,9 +453,6 @@ class Zigbee {
|
||||
} else {
|
||||
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
|
||||
lqiScanList.clear();
|
||||
rtgScanList.clear();
|
||||
collateMap();
|
||||
}
|
||||
}, lqiScanList.size * 1000);
|
||||
|
||||
Reference in New Issue
Block a user