Skip to content

Commit

Permalink
Use lastSeen from zigbee-herdsman.
Browse files Browse the repository at this point in the history
  • Loading branch information
Koenkk committed Sep 12, 2019
1 parent 798a4ee commit 9c5b3bf
Show file tree
Hide file tree
Showing 12 changed files with 46 additions and 79 deletions.
24 changes: 14 additions & 10 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,47 +230,51 @@ class Controller {
}

async publishEntityState(IDorName, payload) {
const entity = settings.getEntity(IDorName);
if (!entity) {
const entity = await this.zigbee.resolveEntity(IDorName);
if (!entity || !entity.settings) {
logger.error(`'${IDorName}' does not exist, skipping publish`);
return;
}

let messagePayload = {...payload};
const currentState = this.state.exists(entity.ID) ? this.state.get(entity.ID) : {};
const currentState = this.state.exists(entity.settings.ID) ? this.state.get(entity.settings.ID) : {};
const newState = objectAssignDeep.noMutate(currentState, payload);

// Update state cache with new state.
this.state.set(entity.ID, newState);
this.state.set(entity.settings.ID, newState);

if (settings.get().advanced.cache_state) {
// Add cached state to payload
messagePayload = newState;
}

const options = {
retain: entity.hasOwnProperty('retain') ? entity.retain : false,
qos: entity.hasOwnProperty('qos') ? entity.qos : 0,
retain: entity.settings.hasOwnProperty('retain') ? entity.settings.retain : false,
qos: entity.settings.hasOwnProperty('qos') ? entity.settings.qos : 0,
};

if (entity.type === 'device' && settings.get().mqtt.include_device_information) {
const device = await this.zigbee.getDevice({ieeeAddr: entity.ID});
const device = await this.zigbee.getDevice({ieeeAddr: entity.device.ieeeAddr});
const attributes = [
'ieeeAddr', 'networkAddress', 'type', 'manufacturerID', 'manufacturerName', 'powerSource',
'applicationVersion', 'stackVersion', 'zclVersion', 'hardwareVersion', 'dateCode', 'softwareBuildID',
];

messagePayload.device = {friendlyName: entity.friendly_name};
messagePayload.device = {friendlyName: entity.name};
attributes.forEach((a) => messagePayload.device[a] = device[a]);
}

if (entity.type === 'device' && settings.get().advanced.last_seen !== 'disable') {
messagePayload.last_seen = utils.formatDate(entity.device.lastSeen, settings.get().advanced.last_seen);
}

if (Object.entries(messagePayload).length) {
if (settings.get().experimental.output === 'json') {
await this.mqtt.publish(entity.friendly_name, JSON.stringify(messagePayload), options);
await this.mqtt.publish(entity.name, JSON.stringify(messagePayload), options);
} else {
/* istanbul ignore else */
if (settings.get().experimental.output === 'attribute') {
await this.iteratePayloadAttributeOutput(`${entity.friendly_name}/`, messagePayload, options);
await this.iteratePayloadAttributeOutput(`${entity.name}/`, messagePayload, options);
}
}
}
Expand Down
7 changes: 1 addition & 6 deletions lib/extension/deviceReceive.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,8 @@ class DeviceReceive {
payload.linkquality = data.linkquality;
}

// Add last seen timestamp
const now = Date.now();
if (settings.get().advanced.last_seen !== 'disable') {
payload.last_seen = utils.formatDate(now, settings.get().advanced.last_seen);
}

if (settings.get().advanced.elapsed) {
const now = Date.now();
if (this.elapsed[data.device.ieeeAddr]) {
payload.elapsed = now - this.elapsed[data.device.ieeeAddr];
}
Expand Down
4 changes: 0 additions & 4 deletions lib/extension/entityPublish.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,6 @@ class EntityPublish {
delete msg.state;
}

if (settings.get().advanced.last_seen !== 'disable') {
msg.last_seen = utils.formatDate(Date.now(), settings.get().advanced.last_seen);
}

this.publishEntityState(entity.settings.ID, msg);
}

Expand Down
11 changes: 2 additions & 9 deletions lib/extension/networkMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ class NetworkMap {
this.zigbee = zigbee;
this.mqtt = mqtt;
this.state = state;
this.lastSeen = new Map();

// Subscribe to topic.
this.topic = `${settings.get().mqtt.base_topic}/bridge/networkmap`;
Expand All @@ -25,12 +24,6 @@ class NetworkMap {
};
}

onZigbeeEvent(type, data, mappedDevice, settingsDevice) {
if (data.device) {
this.lastSeen.set(data.device.ieeeAddr, Date.now());
}
}

onMQTTConnected() {
this.mqtt.subscribe(this.topic);
this.mqtt.subscribe(this.topicRoutes);
Expand Down Expand Up @@ -80,7 +73,7 @@ class NetworkMap {

// Add the device last_seen timestamp
let lastSeen = 'unknown';
const date = device.type === 'Coordinator' ? Date.now() : this.lastSeen.get(device.ieeeAddr);
const date = device.type === 'Coordinator' ? Date.now() : device.lastSeen;
if (date) {
const lastSeenAgo = `${new Date(Date.now() - date).toISOString().substr(11, 8)}s ago`;
lastSeen = utils.formatDate(date, settings.get().advanced.last_seen, lastSeenAgo);
Expand Down Expand Up @@ -166,7 +159,7 @@ class NetworkMap {
networkMap.nodes.push({
ieeeAddr: device.ieeeAddr, friendlyName: resolved.name, type: device.type,
networkAddress: device.networkAddress, manufacturerName: device.manufacturerName,
modelID: device.modelID, failed: failed.get(device),
modelID: device.modelID, failed: failed.get(device), lastSeen: device.lastSeen,
});
}

Expand Down
24 changes: 0 additions & 24 deletions lib/state.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const logger = require('./util/logger');
const data = require('./util/data');
const fs = require('fs');
const settings = require('./util/settings');
const objectAssignDeep = require('object-assign-deep');

const saveInterval = 1000 * 60 * 5; // 5 minutes
Expand All @@ -18,8 +17,6 @@ class State {
this.state = {};
this.file = data.joinPath('state.json');
this.timer = null;

this.handleSettingsChanged = this.handleSettingsChanged.bind(this);
}

start() {
Expand All @@ -28,27 +25,6 @@ class State {
// Save the state on every interval
this.clearTimer();
this.timer = setInterval(() => this.save(), saveInterval);

// Listen for on settings changed events.
settings.addOnChangeHandler(this.handleSettingsChanged);

this.checkLastSeen();
}

handleSettingsChanged() {
this.checkLastSeen();
}

checkLastSeen() {
if (settings.get().advanced.last_seen === 'disable') {
Object.values(this.state).forEach((s) => {
if (s.hasOwnProperty('last_seen')) {
delete s.last_seen;
}
});

this.save();
}
}

clearTimer() {
Expand Down
8 changes: 0 additions & 8 deletions lib/util/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const file = data.joinPath('configuration.yaml');
const objectAssignDeep = require(`object-assign-deep`);
const path = require('path');
const yaml = require('./yaml');
const onChangeHandlers = [];

const defaults = {
whitelist: [],
Expand Down Expand Up @@ -127,7 +126,6 @@ function write() {

_settings = read();
_settingsWithDefaults = objectAssignDeep.noMutate(defaults, get());
onChangeHandlers.forEach((handler) => handler());
}

function read() {
Expand Down Expand Up @@ -387,11 +385,6 @@ function changeFriendlyName(IDorName, newName) {
write();
}

function addOnChangeHandler(handler) {
onChangeHandlers.push(handler);
}


module.exports = {
get: getWithDefaults,
set,
Expand All @@ -408,7 +401,6 @@ module.exports = {
addDeviceToGroup,
removeDeviceFromGroup,
changeDeviceOptions,
addOnChangeHandler,
changeFriendlyName,

// For tests only
Expand Down
11 changes: 9 additions & 2 deletions lib/zigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,16 @@ class Zigbee extends events.EventEmitter {
}

async resolveEntity(key) {
assert(typeof key === 'string' || key.constructor.name === 'Device', `Wrong type '${typeof key}'`);
assert(
typeof key === 'string' || typeof key === 'number' ||
key.constructor.name === 'Device', `Wrong type '${typeof key}'`
);

if (typeof key === 'string' || typeof key === 'number') {
if (typeof key === 'number') {
key = key.toString();
}

if (typeof key === 'string') {
if (key === 'coordinator') {
const coordinator = await this.getDevice({type: 'Coordinator'});
return {
Expand Down
6 changes: 3 additions & 3 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"rimraf": "*",
"semver": "*",
"winston": "2.4.2",
"zigbee-herdsman": "0.6.7",
"zigbee-herdsman": "0.6.8",
"zigbee-herdsman-converters": "11.0.9"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions test/deviceReceive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ describe('Device receive', () => {
jest.advanceTimersByTime(50);
expect(MQTT.publish).toHaveBeenCalledTimes(0);
jest.runAllTimers();
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledTimes(1);
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/weather_sensor');
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2, linkquality: 10});
Expand Down
26 changes: 14 additions & 12 deletions test/networkmap.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ 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', () => {
Expand Down Expand Up @@ -86,12 +87,12 @@ describe('Networkmap', () => {

const expected = {
"nodes":[
{"ieeeAddr":coordinator.ieeeAddr,"friendlyName":"Coordinator","type":"Coordinator","networkAddress":0,"modelID":null,"failed":[]},
{"ieeeAddr":bulb.ieeeAddr,"friendlyName":"bulb","type":"Router","networkAddress":40369,"modelID":"TRADFRI bulb E27 WS opal 980lm","failed":[]},
{"ieeeAddr":bulb_color.ieeeAddr,"friendlyName":"bulb_color","type":"Router","networkAddress":40399,"modelID":"LLC020","failed":[]},
{"ieeeAddr":WXKG02LM.ieeeAddr,"friendlyName":"button_double_key","type":"EndDevice","networkAddress":6538,"modelID":"lumi.sensor_86sw2.es1"},
{"ieeeAddr":unsupported_router.ieeeAddr,"friendlyName":"0x0017880104e45525","type":"Router","networkAddress":6536,"modelID":"notSupportedModelID","manufacturerName": "Boef","failed":['lqi', 'routingTable']},
{"ieeeAddr":CC2530_ROUTER.ieeeAddr,"friendlyName":"cc2530_router","type":"Router","networkAddress":6540,"modelID":"lumi.router","failed":[]},
{"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)},
Expand Down Expand Up @@ -128,6 +129,7 @@ describe('Networkmap', () => {
it('Output graphviz networkmap', 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};
Expand All @@ -141,15 +143,15 @@ describe('Networkmap', () => {
const expected = `digraph G {
node[shape=record];
"0x00124b00120144ae" [style="bold, filled", fillcolor="#e04e5d", fontcolor="#ffffff", label="{Coordinator|0x00124b00120144ae (0)|Last seen: 00:00:00s 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)|Last seen: unknown}"];
"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)|Last seen: 00:00:09s 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)|Last seen: 00:00:00s ago}"];
"0x000b57fffec6a5b3" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{bulb_color|0x000b57fffec6a5b3 (40399)|Philips Hue Go (7146060PH)|Last seen: 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)|Last seen: unknown}"];
"0x0017880104e45521" [style="rounded, dashed, filled", fillcolor="#fff8ce", fontcolor="#000000", label="{button_double_key|0x0017880104e45521 (6538)|Xiaomi Aqara double key wireless wall switch (WXKG02LM)|Last seen: 00:00:09s 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|Last seen: unknown}"];
"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)|Last seen: unknown}"];
"0x0017880104e45525" [style="rounded, filled", fillcolor="#4ea3e0", fontcolor="#ffffff", label="{0x0017880104e45525|0x0017880104e45525 (6536)failed: lqi,routingTable|Boef notSupportedModelID|Last seen: 00:00:09s 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)|Last seen: 00:00:09s ago}"];
"0x0017880104e45559" -> "0x000b57fffec6a5b2" [penwidth=0.5, weight=0, color="#994444", label="100"]
}`;

Expand Down
1 change: 1 addition & 0 deletions test/stub/zigbeeHerdsman.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Device {
this.removeFromNetwork = jest.fn();
this.save = jest.fn();
this.manufacturerName = manufacturerName;
this.lastSeen = 1000;
}

getEndpoint(ID) {
Expand Down

0 comments on commit 9c5b3bf

Please sign in to comment.