diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index a3fe4a966..138b3b50d 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -55,4 +55,7 @@ advanced: soft_reset_timeout: 0 # Optional: network encryption key, changing requires repairing of all devices. network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13], + # Optional: Add a last_seen attribute to MQTT messages, contains date/time of last Zigbee message + # possible values are: disable (default), ISO_8601, epoch + last_seen: 'disable' ``` \ No newline at end of file diff --git a/lib/extension/deviceReceive.js b/lib/extension/deviceReceive.js index b819c1ae4..95f670b0b 100644 --- a/lib/extension/deviceReceive.js +++ b/lib/extension/deviceReceive.js @@ -103,8 +103,14 @@ class DeviceReceive { } // Add last seen timestamp - const now = new Date(); - payload.last_seen = now.toISOString(); + switch (settings.get().advanced.last_seen) { + case 'ISO_8601': + payload.last_seen = new Date().toISOString(); + break; + case 'epoch': + payload.last_seen = Date.now(); + break; + } this.publishDeviceState(device, payload, cache); }; diff --git a/lib/util/settings.js b/lib/util/settings.js index 27cdceeae..052dbb9cb 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -31,6 +31,14 @@ const defaults = { */ cache_state: true, + /** + * Add a last_seen attribute to mqtt messages, contains date/time of zigbee message arrival + * "ISO_8601": ISO 8601 format + * "epoch": milliseconds elapsed since the UNIX epoch + * "disable": no last_seen attribute (default) + */ + last_seen: 'disable', + /** * https://github.com/Koenkk/zigbee2mqtt/issues/685#issuecomment-449112250 * diff --git a/test/controller.test.js b/test/controller.test.js index 3ec3785b6..111159ad7 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -30,7 +30,7 @@ describe('Controller', () => { controller.onZigbeeMessage(message); chai.assert.isTrue(mqttPublish.calledOnce); chai.assert.strictEqual( - utils.withoutLastSeen(mqttPublish.getCall(0).args[1]), + mqttPublish.getCall(0).args[1], JSON.stringify({state: 'ON'}) ); }); @@ -52,7 +52,7 @@ describe('Controller', () => { controller.onZigbeeMessage(message); chai.assert.isTrue(mqttPublish.calledOnce); chai.assert.strictEqual( - utils.withoutLastSeen(mqttPublish.getCall(0).args[1]), + mqttPublish.getCall(0).args[1], `{"state":"ON","device":{"ieeeAddr":"0x12345678","friendlyName":"test",` + `"modelId":"TRADFRI bulb E27 CWS opal 600lm"}}` ); diff --git a/test/deviceReceive.test.js b/test/deviceReceive.test.js index 4a1eaa607..17bae682c 100644 --- a/test/deviceReceive.test.js +++ b/test/deviceReceive.test.js @@ -37,7 +37,7 @@ describe('DeviceReceive', () => { const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 1); deviceReceive.onZigbeeMessage(message, device, WXKG11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {click: 'single'}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {click: 'single'}); }); it('Should handle a zigbee message which uses ep (left)', () => { @@ -45,7 +45,7 @@ describe('DeviceReceive', () => { const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 1); deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {click: 'left'}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {click: 'left'}); }); it('Should handle a zigbee message which uses ep (right)', () => { @@ -53,7 +53,7 @@ describe('DeviceReceive', () => { const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 2); deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {click: 'right'}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {click: 'right'}); }); it('Should handle a zigbee message with default precision', () => { @@ -63,7 +63,7 @@ describe('DeviceReceive', () => { ); deviceReceive.onZigbeeMessage(message, device, WSDCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {temperature: -0.85}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {temperature: -0.85}); }); it('Should handle a zigbee message with 1 precision', () => { @@ -76,7 +76,7 @@ describe('DeviceReceive', () => { ); deviceReceive.onZigbeeMessage(message, device, WSDCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {temperature: -0.8}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {temperature: -0.8}); }); it('Should handle a zigbee message with 0 precision', () => { @@ -89,7 +89,7 @@ describe('DeviceReceive', () => { ); deviceReceive.onZigbeeMessage(message, device, WSDCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {temperature: -1}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {temperature: -1}); }); it('Should handle a zigbee message with 1 precision when set via device_options', () => { @@ -99,6 +99,9 @@ describe('DeviceReceive', () => { device_options: { temperature_precision: 1, }, + advanced: { + last_seen: 'disable', + }, }; }); sandbox.stub(settings, 'getDevice').callsFake(() => { @@ -109,7 +112,7 @@ describe('DeviceReceive', () => { ); deviceReceive.onZigbeeMessage(message, device, WSDCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {temperature: -0.8}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {temperature: -0.8}); }); it('Should handle a zigbee message with 2 precision when overrides device_options', () => { @@ -119,6 +122,9 @@ describe('DeviceReceive', () => { device_options: { temperature_precision: 1, }, + advanced: { + last_seen: 'disable', + }, }; }); sandbox.stub(settings, 'getDevice').callsFake(() => { @@ -131,7 +137,7 @@ describe('DeviceReceive', () => { ); deviceReceive.onZigbeeMessage(message, device, WSDCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), {temperature: -0.85}); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], {temperature: -0.85}); }); it('Should handle a zigbee message with voltage 3010', () => { @@ -140,7 +146,7 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); const expected = {battery: 100, voltage: 3010}; - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), expected); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], expected); }); it('Should handle a zigbee message with voltage 2850', () => { @@ -149,7 +155,7 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); const expected = {battery: 35, voltage: 2850}; - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), expected); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], expected); }); it('Should handle a zigbee message with voltage 2650', () => { @@ -158,7 +164,7 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); const expected = {battery: 14, voltage: 2650}; - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), expected); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], expected); }); it('Should handle a zigbee message with voltage 2000', () => { @@ -167,7 +173,7 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, WXKG02LM); chai.assert.isTrue(publishDeviceState.calledOnce); const expected = {battery: 0, voltage: 2000}; - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), expected); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], expected); }); it('Should publish 1 message when converted twice', () => { @@ -179,7 +185,7 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, RTCGQ11LM); chai.assert.isTrue(publishDeviceState.calledOnce); const expected = {'battery': 100, 'illuminance': 381, 'voltage': 3045}; - chai.assert.deepEqual(utils.withoutLastSeen(publishDeviceState.getCall(0).args[1]), expected); + chai.assert.deepEqual(publishDeviceState.getCall(0).args[1], expected); }); it('Should publish no message when converted without result', () => { @@ -189,5 +195,35 @@ describe('DeviceReceive', () => { deviceReceive.onZigbeeMessage(message, device, RTCGQ11LM); chai.assert.isTrue(publishDeviceState.notCalled); }); + + it('Should publish last_seen epoch', () => { + const device = {ieeeAddr: '0x12345678'}; + const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 1); + sandbox.stub(settings, 'get').callsFake(() => { + return { + advanced: { + last_seen: 'epoch', + }, + }; + }); + deviceReceive.onZigbeeMessage(message, device, WXKG02LM); + chai.assert.isTrue(publishDeviceState.calledOnce); + chai.assert.equal(typeof publishDeviceState.getCall(0).args[1].last_seen, 'number'); + }); + + it('Should publish last_seen ISO_8601', () => { + const device = {ieeeAddr: '0x12345678'}; + const message = utils.zigbeeMessage(device, 'genOnOff', 'attReport', {onOff: 1}, 1); + sandbox.stub(settings, 'get').callsFake(() => { + return { + advanced: { + last_seen: 'ISO_8601', + }, + }; + }); + deviceReceive.onZigbeeMessage(message, device, WXKG02LM); + chai.assert.isTrue(publishDeviceState.calledOnce); + chai.assert.equal(typeof publishDeviceState.getCall(0).args[1].last_seen, 'string'); + }); }); }); diff --git a/test/utils.js b/test/utils.js index bab522d0e..086ce58b4 100644 --- a/test/utils.js +++ b/test/utils.js @@ -10,19 +10,4 @@ module.exports = { zigbeeMessage: (device, cid, type, data, epId) => { return {data: {cid: cid, data: data}, type: type, endpoints: [{device: device, epId: epId}]}; }, - withoutLastSeen: (obj) => { - const isString = typeof obj === 'string'; - - if (isString) { - obj = JSON.parse(obj); - } - - delete obj.last_seen; - - if (isString) { - obj = JSON.stringify(obj); - } - - return obj; - }, };