mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-07-19 01:56:29 +00:00
Use lastSeen from zigbee-herdsman.
This commit is contained in:
+14
-10
@@ -230,18 +230,18 @@ 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
|
||||
@@ -249,28 +249,32 @@ class Controller {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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`;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -18,8 +17,6 @@ class State {
|
||||
this.state = {};
|
||||
this.file = data.joinPath('state.json');
|
||||
this.timer = null;
|
||||
|
||||
this.handleSettingsChanged = this.handleSettingsChanged.bind(this);
|
||||
}
|
||||
|
||||
start() {
|
||||
@@ -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() {
|
||||
|
||||
@@ -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: [],
|
||||
@@ -127,7 +126,6 @@ function write() {
|
||||
|
||||
_settings = read();
|
||||
_settingsWithDefaults = objectAssignDeep.noMutate(defaults, get());
|
||||
onChangeHandlers.forEach((handler) => handler());
|
||||
}
|
||||
|
||||
function read() {
|
||||
@@ -387,11 +385,6 @@ function changeFriendlyName(IDorName, newName) {
|
||||
write();
|
||||
}
|
||||
|
||||
function addOnChangeHandler(handler) {
|
||||
onChangeHandlers.push(handler);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
get: getWithDefaults,
|
||||
set,
|
||||
@@ -408,7 +401,6 @@ module.exports = {
|
||||
addDeviceToGroup,
|
||||
removeDeviceFromGroup,
|
||||
changeDeviceOptions,
|
||||
addOnChangeHandler,
|
||||
changeFriendlyName,
|
||||
|
||||
// For tests only
|
||||
|
||||
+9
-2
@@ -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 {
|
||||
|
||||
Generated
+3
-3
@@ -5938,9 +5938,9 @@
|
||||
}
|
||||
},
|
||||
"zigbee-herdsman": {
|
||||
"version": "0.6.7",
|
||||
"resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.6.7.tgz",
|
||||
"integrity": "sha512-DtgI9Gq5fvBtgW8EUT/Gb1hEtzesOyWSQExiS6fGQaDNq17/6Wy78NUkVkYLUTYRTz5QS9HTqWPEqQFv8qjbFg==",
|
||||
"version": "0.6.8",
|
||||
"resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.6.8.tgz",
|
||||
"integrity": "sha512-S96Bq9UD1jnPe8G4lg5Rarq3ZiCw/ryFEeyvKtm6p5hN83yqRx/YdbDQNW2/+or1My0zbIfG/06MyjsoMtn/dQ==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"fast-deep-equal": "^2.0.1",
|
||||
|
||||
+1
-1
@@ -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": {
|
||||
|
||||
@@ -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});
|
||||
|
||||
+14
-12
@@ -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', () => {
|
||||
@@ -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)},
|
||||
@@ -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};
|
||||
@@ -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"]
|
||||
}`;
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class Device {
|
||||
this.removeFromNetwork = jest.fn();
|
||||
this.save = jest.fn();
|
||||
this.manufacturerName = manufacturerName;
|
||||
this.lastSeen = 1000;
|
||||
}
|
||||
|
||||
getEndpoint(ID) {
|
||||
|
||||
Reference in New Issue
Block a user