mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2024-11-16 10:28:33 -07:00
312 lines
17 KiB
JavaScript
312 lines
17 KiB
JavaScript
const data = require('./stub/data');
|
|
const logger = require('./stub/logger');
|
|
const zigbeeHerdsman = require('./stub/zigbeeHerdsman');
|
|
const stringify = require('json-stable-stringify');
|
|
|
|
const {coordinator, bulb, bulb_color, WXKG02LM, CC2530_ROUTER, unsupported_router} = zigbeeHerdsman.devices;
|
|
|
|
zigbeeHerdsman.returnDevices.push(coordinator.ieeeAddr);
|
|
zigbeeHerdsman.returnDevices.push(bulb.ieeeAddr);
|
|
zigbeeHerdsman.returnDevices.push(bulb_color.ieeeAddr);
|
|
zigbeeHerdsman.returnDevices.push(WXKG02LM.ieeeAddr);
|
|
zigbeeHerdsman.returnDevices.push(CC2530_ROUTER.ieeeAddr);
|
|
zigbeeHerdsman.returnDevices.push(unsupported_router.ieeeAddr);
|
|
const MQTT = require('./stub/mqtt');
|
|
const settings = require('../lib/util/settings');
|
|
const Controller = require('../lib/controller');
|
|
const flushPromises = () => new Promise(setImmediate);
|
|
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
Date.now = jest.fn()
|
|
Date.now.mockReturnValue(10000);
|
|
const mocksClear = [MQTT.publish, logger.warn, logger.debug];
|
|
|
|
describe('Networkmap', () => {
|
|
let controller;
|
|
|
|
beforeAll(async () => {
|
|
data.writeDefaultConfiguration();
|
|
settings._reRead();
|
|
data.writeEmptyState();
|
|
settings.set(['experimental', 'new_api'], true);
|
|
controller = new Controller();
|
|
await controller.start();
|
|
mocksClear.forEach((m) => m.mockClear());
|
|
await flushPromises();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
mocksClear.forEach((m) => m.mockClear());
|
|
await flushPromises();
|
|
});
|
|
|
|
function mock() {
|
|
/**
|
|
* Topology
|
|
*
|
|
* | -> bulb_color -> unsupported_router (offline)
|
|
* coordinator | ^ ^
|
|
* | | | (not valid)
|
|
* | -> bulb |
|
|
* | -> CC2530_ROUTER -> WXKG02LM
|
|
*
|
|
*/
|
|
coordinator.lqi = () => {return {neighbors: [
|
|
{ieeeAddr: bulb_color.ieeeAddr, networkAddress: bulb_color.networkAddress, relationship: 2, depth: 1, linkquality: 120},
|
|
{ieeeAddr: bulb.ieeeAddr, networkAddress: bulb.networkAddress, relationship: 2, depth: 1, linkquality: 92}
|
|
]}};
|
|
coordinator.routingTable = () => {return {table: [
|
|
{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress},
|
|
]}};
|
|
|
|
bulb.lqi = () => {return {neighbors: [
|
|
{ieeeAddr: bulb_color.ieeeAddr, networkAddress: bulb_color.networkAddress, relationship: 1, depth: 2, linkquality: 110},
|
|
{ieeeAddr: CC2530_ROUTER.ieeeAddr, networkAddress: CC2530_ROUTER.networkAddress, relationship: 1, depth: 2, linkquality: 100}
|
|
]}};
|
|
bulb.routingTable = () => {return {table: []}};
|
|
|
|
bulb_color.lqi = () => {return {neighbors: []}}
|
|
bulb_color.routingTable = () => {return {table: []}};
|
|
|
|
CC2530_ROUTER.lqi = () => {return {neighbors: [
|
|
{ieeeAddr: WXKG02LM.ieeeAddr, networkAddress: WXKG02LM.networkAddress, relationship: 1, depth: 2, linkquality: 130},
|
|
{ieeeAddr: bulb_color.ieeeAddr, networkAddress: bulb_color.networkAddress, relationship: 4, depth: 2, linkquality: 130},
|
|
]}};
|
|
CC2530_ROUTER.routingTable = () => {return {table: []}};
|
|
|
|
unsupported_router.lqi = () => {throw new Error('failed')};
|
|
unsupported_router.routingTable = () => {throw new Error('failed')};
|
|
}
|
|
|
|
it('Output raw networkmap legacy api', async () => {
|
|
mock();
|
|
MQTT.events.message('zigbee2mqtt/bridge/networkmap/routes', 'raw');
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
let call = MQTT.publish.mock.calls[0];
|
|
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw');
|
|
|
|
const conv = (device) => {return {ieeeAddr: device.ieeeAddr, networkAddress: device.networkAddress}};
|
|
|
|
const expected = {
|
|
"nodes":[
|
|
{"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
|
|
{"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
|
|
{"lastSeen": 1000,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
|
|
{"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
|
|
{"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
|
|
{"lastSeen": 1000,"ieeeAddr":CC2530_ROUTER.ieeeAddr,"friendlyName":"cc2530_router","type":"Router","networkAddress":6540,"modelID":"lumi.router","failed":[]},
|
|
],
|
|
"links":[
|
|
{depth: 1, linkquality: 120, routes: [], source: conv(bulb_color), target: conv(coordinator), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 120, relationship: 2},
|
|
{depth: 1, linkquality: 92, routes: [{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress}], source: conv(bulb), target: conv(coordinator), sourceIeeeAddr: bulb.ieeeAddr, sourceNwkAddr: bulb.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 92, relationship: 2},
|
|
{depth: 2, linkquality: 110, routes: [], source: conv(bulb_color), target: conv(bulb), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 110, relationship: 1},
|
|
{depth: 2, linkquality: 100, routes: [], source: conv(CC2530_ROUTER), target: conv(bulb), sourceIeeeAddr: CC2530_ROUTER.ieeeAddr, sourceNwkAddr: CC2530_ROUTER.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 100, relationship: 1},
|
|
{depth: 2, linkquality: 130, routes: [], source: conv(WXKG02LM), target: conv(CC2530_ROUTER), sourceIeeeAddr: WXKG02LM.ieeeAddr, sourceNwkAddr: WXKG02LM.networkAddress, targetIeeeAddr: CC2530_ROUTER.ieeeAddr, lqi: 130, relationship: 1}
|
|
]
|
|
};
|
|
|
|
expect(JSON.parse(call[1])).toStrictEqual(expected);
|
|
|
|
/**
|
|
* Check again without routes
|
|
*/
|
|
MQTT.publish.mockClear();
|
|
MQTT.events.message('zigbee2mqtt/bridge/networkmap', 'raw');
|
|
await flushPromises();
|
|
call = MQTT.publish.mock.calls[0];
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/raw');
|
|
|
|
// Remove routing information
|
|
expected.nodes.forEach((n) => {
|
|
if (n.failed && n.failed.includes('routingTable')) {
|
|
n.failed.splice(n.failed.indexOf('routingTable'), 1);
|
|
}
|
|
});
|
|
|
|
expected.links.forEach((l) => l.routes = [])
|
|
expect(JSON.parse(call[1])).toStrictEqual(expected);
|
|
});
|
|
|
|
it('Output graphviz networkmap legacy api', async () => {
|
|
mock();
|
|
const device = zigbeeHerdsman.devices.bulb_color;
|
|
device.lastSeen = null;
|
|
const endpoint = device.getEndpoint(1);
|
|
const data = {modelID: 'test'}
|
|
const payload = {data, cluster: 'genOnOff', device, endpoint, type: 'readResponse', linkquality: 10};
|
|
await zigbeeHerdsman.events.message(payload);
|
|
MQTT.events.message('zigbee2mqtt/bridge/networkmap/routes', 'graphviz');
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
let call = MQTT.publish.mock.calls[0];
|
|
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/graphviz');
|
|
|
|
const expected = `digraph G {
|
|
node[shape=record];
|
|
"0x00124b00120144ae" [style="bold, filled", fillcolor="#e04e5d", fontcolor="#ffffff", label="{Coordinator|0x00124b00120144ae (0)|0 seconds ago}"];
|
|
"0x000b57fffec6a5b2" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb|0x000b57fffec6a5b2 (40369)|IKEA TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)|9 seconds ago}"];
|
|
"0x000b57fffec6a5b2" -> "0x00124b00120144ae" [penwidth=2, weight=1, color="#009900", label="92 (routes: 6540)"]
|
|
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (40399)|Philips Hue Go (7146060PH)|unknown}"];
|
|
"0x000b57fffec6a5b3" -> "0x00124b00120144ae" [penwidth=0.5, weight=0, color="#994444", label="120"]
|
|
"0x000b57fffec6a5b3" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="110"]
|
|
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (6538)|Xiaomi Aqara double key wireless wall switch (WXKG02LM)|9 seconds ago}"];
|
|
"0x0017880104e45521" -> "0x0017880104e45559" [penwidth=1, weight=0, color="#994444", label="130"]
|
|
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (6536)failed: lqi,routingTable|Boef notSupportedModelID|9 seconds ago}"];
|
|
"0x0017880104e45559" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{cc2530_router|0x0017880104e45559 (6540)|Custom devices (DiY) [CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/) (CC2530.ROUTER)|9 seconds ago}"];
|
|
"0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"]
|
|
}`;
|
|
|
|
const expectedLines = expected.split('\n');
|
|
const actualLines = call[1].split('\n');
|
|
|
|
for (let i = 0; i < expectedLines.length; i++) {
|
|
expect(actualLines[i].trim()).toStrictEqual(expectedLines[i].trim());
|
|
}
|
|
});
|
|
|
|
it('Output plantuml networkmap legacy api', async () => {
|
|
mock();
|
|
const device = zigbeeHerdsman.devices.bulb_color;
|
|
device.lastSeen = null;
|
|
const endpoint = device.getEndpoint(1);
|
|
const data = {modelID: 'test'}
|
|
const payload = {data, cluster: 'genOnOff', device, endpoint, type: 'readResponse', linkquality: 10};
|
|
await zigbeeHerdsman.events.message(payload);
|
|
MQTT.events.message('zigbee2mqtt/bridge/networkmap/routes', 'plantuml');
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
let call = MQTT.publish.mock.calls[0];
|
|
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/networkmap/plantuml');
|
|
|
|
const expected = `' paste into: https://www.planttext.com/
|
|
|
|
@startuml
|
|
card 0x0017880104e45525 [
|
|
0x0017880104e45525
|
|
---
|
|
0x0017880104e45525 (6536) failed: lqi,routingTable
|
|
---
|
|
Boef notSupportedModelID
|
|
---
|
|
9 seconds ago
|
|
]
|
|
|
|
card 0x000b57fffec6a5b2 [
|
|
bulb
|
|
---
|
|
0x000b57fffec6a5b2 (40369)
|
|
---
|
|
IKEA TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)
|
|
---
|
|
9 seconds ago
|
|
]
|
|
|
|
card 0x000b57fffec6a5b3 [
|
|
bulb_color
|
|
---
|
|
0x000b57fffec6a5b3 (40399)
|
|
---
|
|
Philips Hue Go (7146060PH)
|
|
---
|
|
unknown
|
|
]
|
|
|
|
card 0x0017880104e45521 [
|
|
button_double_key
|
|
---
|
|
0x0017880104e45521 (6538)
|
|
---
|
|
Xiaomi Aqara double key wireless wall switch (WXKG02LM)
|
|
---
|
|
9 seconds ago
|
|
]
|
|
|
|
card 0x0017880104e45559 [
|
|
cc2530_router
|
|
---
|
|
0x0017880104e45559 (6540)
|
|
---
|
|
Custom devices (DiY) [CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/) (CC2530.ROUTER)
|
|
---
|
|
9 seconds ago
|
|
]
|
|
|
|
card 0x00124b00120144ae [
|
|
Coordinator
|
|
---
|
|
0x00124b00120144ae (0)
|
|
---
|
|
0 seconds ago
|
|
]
|
|
|
|
0x000b57fffec6a5b3 --> 0x00124b00120144ae: 120
|
|
0x000b57fffec6a5b2 --> 0x00124b00120144ae: 92
|
|
0x000b57fffec6a5b3 --> 0x000b57fffec6a5b2: 110
|
|
0x0017880104e45559 --> 0x000b57fffec6a5b2: 100
|
|
0x0017880104e45521 --> 0x0017880104e45559: 130
|
|
|
|
@enduml`;
|
|
|
|
const expectedLines = expected.split('\n');
|
|
const actualLines = call[1].split('\n');
|
|
|
|
for (let i = 0; i < expectedLines.length; i++) {
|
|
expect(actualLines[i].trim()).toStrictEqual(expectedLines[i].trim());
|
|
}
|
|
});
|
|
|
|
it('Should output raw networkmap', async () => {
|
|
mock();
|
|
MQTT.publish.mockClear();
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true}));
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
|
let call = MQTT.publish.mock.calls[0];
|
|
expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap');
|
|
|
|
const conv = (device) => {return {ieeeAddr: device.ieeeAddr, networkAddress: device.networkAddress}};
|
|
|
|
const expected = {
|
|
status: 'ok',
|
|
data: {
|
|
type: 'raw',
|
|
routes: true,
|
|
value: {
|
|
"nodes":[
|
|
{"lastSeen": 1000,"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
|
|
{"lastSeen": 1000,"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
|
|
{"lastSeen": null,"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
|
|
{"lastSeen": 1000,"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
|
|
{"lastSeen": 1000,"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
|
|
{"lastSeen": 1000,"ieeeAddr":CC2530_ROUTER.ieeeAddr,"friendlyName":"cc2530_router","type":"Router","networkAddress":6540,"modelID":"lumi.router","failed":[]},
|
|
],
|
|
"links":[
|
|
{depth: 1, linkquality: 120, routes: [], source: conv(bulb_color), target: conv(coordinator), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 120, relationship: 2},
|
|
{depth: 1, linkquality: 92, routes: [{destinationAddress: CC2530_ROUTER.networkAddress, status: 'ACTIVE', nextHop: bulb.networkAddress}], source: conv(bulb), target: conv(coordinator), sourceIeeeAddr: bulb.ieeeAddr, sourceNwkAddr: bulb.networkAddress, targetIeeeAddr: coordinator.ieeeAddr, lqi: 92, relationship: 2},
|
|
{depth: 2, linkquality: 110, routes: [], source: conv(bulb_color), target: conv(bulb), sourceIeeeAddr: bulb_color.ieeeAddr, sourceNwkAddr: bulb_color.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 110, relationship: 1},
|
|
{depth: 2, linkquality: 100, routes: [], source: conv(CC2530_ROUTER), target: conv(bulb), sourceIeeeAddr: CC2530_ROUTER.ieeeAddr, sourceNwkAddr: CC2530_ROUTER.networkAddress, targetIeeeAddr: bulb.ieeeAddr, lqi: 100, relationship: 1},
|
|
{depth: 2, linkquality: 130, routes: [], source: conv(WXKG02LM), target: conv(CC2530_ROUTER), sourceIeeeAddr: WXKG02LM.ieeeAddr, sourceNwkAddr: WXKG02LM.networkAddress, targetIeeeAddr: CC2530_ROUTER.ieeeAddr, lqi: 130, relationship: 1}
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
const actual = JSON.parse(call[1]);
|
|
actual.data.value = JSON.parse(actual.data.value);
|
|
expect(actual).toStrictEqual(expected);
|
|
});
|
|
|
|
it('Should throw error when rquesting invalid type', async () => {
|
|
mock();
|
|
MQTT.publish.mockClear();
|
|
MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', 'not_existing');
|
|
await flushPromises();
|
|
expect(MQTT.publish).toHaveBeenCalledWith(
|
|
'zigbee2mqtt/bridge/response/networkmap',
|
|
stringify({"data":{},"status":"error","error":"Type 'not_existing' not supported, allowed are: raw,graphviz,plantuml"}),
|
|
{retain: false, qos: 0}, expect.any(Function)
|
|
);
|
|
});
|
|
});
|