2018-08-28 12:55:00 -07:00
|
|
|
const settings = require('../util/settings');
|
2019-06-04 10:23:58 -07:00
|
|
|
const utils = require('../util/utils');
|
2018-10-07 12:46:54 -07:00
|
|
|
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
|
2018-08-28 12:55:00 -07:00
|
|
|
|
|
|
|
class NetworkMap {
|
2019-02-04 10:36:49 -07:00
|
|
|
constructor(zigbee, mqtt, state, publishEntityState) {
|
2018-08-28 12:55:00 -07:00
|
|
|
this.zigbee = zigbee;
|
|
|
|
this.mqtt = mqtt;
|
|
|
|
this.state = state;
|
2019-06-17 12:28:27 -07:00
|
|
|
this.lastSeenMap = {};
|
2018-08-28 12:55:00 -07:00
|
|
|
|
|
|
|
// Subscribe to topic.
|
|
|
|
this.topic = `${settings.get().mqtt.base_topic}/bridge/networkmap`;
|
|
|
|
|
2019-06-17 12:28:27 -07:00
|
|
|
// Bind
|
|
|
|
this.raw = this.raw.bind(this);
|
|
|
|
this.graphviz = this.graphviz.bind(this);
|
|
|
|
|
2018-08-28 12:55:00 -07:00
|
|
|
// Set supported formats
|
|
|
|
this.supportedFormats = {
|
|
|
|
'raw': this.raw,
|
2018-08-28 13:07:57 -07:00
|
|
|
'graphviz': this.graphviz,
|
2018-08-28 12:55:00 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-06-17 12:28:27 -07:00
|
|
|
onZigbeeMessage(message, device, mappedDevice) {
|
|
|
|
if (device) {
|
|
|
|
this.lastSeenMap[device.ieeeAddr] = Date.now();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 12:23:11 -07:00
|
|
|
onMQTTConnected() {
|
|
|
|
this.mqtt.subscribe(this.topic);
|
|
|
|
}
|
|
|
|
|
|
|
|
onMQTTMessage(topic, message) {
|
2018-08-28 12:55:00 -07:00
|
|
|
message = message.toString();
|
|
|
|
|
|
|
|
if (topic === this.topic && this.supportedFormats.hasOwnProperty(message)) {
|
|
|
|
this.zigbee.networkScan((result)=> {
|
2018-10-07 12:46:54 -07:00
|
|
|
const converted = this.supportedFormats[message](this.zigbee, result);
|
2018-08-28 12:55:00 -07:00
|
|
|
this.mqtt.publish(`bridge/networkmap/${message}`, converted, {});
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2018-10-23 11:39:48 -07:00
|
|
|
|
|
|
|
return false;
|
2018-08-28 12:55:00 -07:00
|
|
|
}
|
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
raw(zigbee, topology) {
|
2018-08-28 12:55:00 -07:00
|
|
|
return JSON.stringify(topology);
|
|
|
|
}
|
2018-08-28 13:07:57 -07:00
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
graphviz(zigbee, topology) {
|
2019-06-26 10:30:38 -07:00
|
|
|
const colors = settings.get().map_options.graphviz.colors;
|
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
let text = 'digraph G {\nnode[shape=record];\n';
|
2019-01-22 12:07:38 -07:00
|
|
|
let devStyle = '';
|
2018-10-07 12:46:54 -07:00
|
|
|
|
2019-07-01 07:10:50 -07:00
|
|
|
topology.nodes.forEach((device) => {
|
2018-10-07 12:46:54 -07:00
|
|
|
const labels = [];
|
|
|
|
|
|
|
|
// Add friendly name
|
2019-07-01 07:10:50 -07:00
|
|
|
labels.push(`${device.friendlyName}`);
|
2018-10-07 12:46:54 -07:00
|
|
|
|
2019-07-01 07:10:50 -07:00
|
|
|
// 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}`);
|
2018-10-07 12:46:54 -07:00
|
|
|
|
|
|
|
// Add the device model
|
|
|
|
const mappedModel = zigbeeShepherdConverters.findByZigbeeModel(device.modelId);
|
|
|
|
if (mappedModel) {
|
|
|
|
labels.push(`${mappedModel.vendor} ${mappedModel.description} (${mappedModel.model})`);
|
|
|
|
} else {
|
|
|
|
// This model is not supported by zigbee-shepherd-converters, add zigbee model information, if available
|
|
|
|
const zigbeeModel = [device.manufName, device.modelId].filter((a) => a).join(' ');
|
|
|
|
labels.push(zigbeeModel ? zigbeeModel : 'No model information available');
|
|
|
|
}
|
|
|
|
|
2019-06-17 12:28:27 -07:00
|
|
|
// Add the device status (online/offline) and last_seen timestamp
|
|
|
|
let lastSeen = 'unknown';
|
|
|
|
if (device.type == 'Coordinator') {
|
|
|
|
lastSeen = utils.formatDate(Date.now(), settings.get().advanced.last_seen,
|
|
|
|
utils.toLocalISOString(new Date(Date.now())));
|
|
|
|
} else {
|
|
|
|
if (this.lastSeenMap[device.ieeeAddr]) {
|
|
|
|
const lastSeenAgo =
|
|
|
|
`${new Date(Date.now() - this.lastSeenMap[device.ieeeAddr]).toISOString().substr(11, 8)}s ago`;
|
|
|
|
lastSeen = utils.formatDate(this.lastSeenMap[device.ieeeAddr],
|
|
|
|
settings.get().advanced.last_seen, lastSeenAgo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
labels.push(`${device.status} (${lastSeen})`);
|
2018-10-07 12:46:54 -07:00
|
|
|
|
2019-01-22 12:07:38 -07:00
|
|
|
// Shape the record according to device type
|
2019-07-01 07:10:50 -07:00
|
|
|
if (device.type == 'Coordinator') {
|
2019-06-26 10:30:38 -07:00
|
|
|
devStyle = `style="bold, filled", fillcolor="${colors.fill.coordinator}", ` +
|
|
|
|
`fontcolor="${colors.font.coordinator}"`;
|
2019-07-01 07:10:50 -07:00
|
|
|
} else if (device.type == 'Router') {
|
2019-06-26 10:30:38 -07:00
|
|
|
devStyle = `style="rounded, filled", fillcolor="${colors.fill.router}", ` +
|
|
|
|
`fontcolor="${colors.font.router}"`;
|
2019-01-22 12:07:38 -07:00
|
|
|
} else {
|
2019-06-26 10:30:38 -07:00
|
|
|
devStyle = `style="rounded, dashed, filled", fillcolor="${colors.fill.enddevice}", `
|
|
|
|
+ `fontcolor="${colors.font.enddevice}"`;
|
2019-01-22 12:07:38 -07:00
|
|
|
}
|
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
// Add the device with its labels to the graph as a node.
|
2019-01-22 12:07:38 -07:00
|
|
|
text += ` "${device.ieeeAddr}" [`+devStyle+`, label="{${labels.join('|')}}"];\n`;
|
2018-10-07 12:46:54 -07:00
|
|
|
|
|
|
|
/**
|
2019-07-01 07:10:50 -07:00
|
|
|
* Add an edge between the device and its child to the graph
|
2018-10-07 12:46:54 -07:00
|
|
|
* 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.
|
|
|
|
*/
|
2019-07-01 07:10:50 -07:00
|
|
|
topology.links.filter((e) => (e.sourceIeeeAddr === device.ieeeAddr) || (e.SourceNwkAddr === device.nwkAddr))
|
|
|
|
.forEach((e) => {
|
2019-07-06 01:09:44 -07:00
|
|
|
const lineStyle = (device.type=='EndDevice') ? 'penwidth=1, '
|
|
|
|
: (!e.routes.length) ? 'penwidth=0.5, ' : 'penwidth=2, ';
|
2019-07-01 07:10:50 -07:00
|
|
|
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`;
|
|
|
|
});
|
2018-08-28 13:07:57 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
text += '}';
|
|
|
|
|
2018-10-07 12:46:54 -07:00
|
|
|
return text.replace(/\0/g, '');
|
2018-08-28 13:07:57 -07:00
|
|
|
}
|
2018-08-28 12:55:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = NetworkMap;
|