From e6044da6045cfeeec47e31d3aa2f9301527e3cc1 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Sat, 16 Mar 2019 23:41:46 +0100 Subject: [PATCH] Implemented device debounce option. #1264 --- lib/extension/deviceReceive.js | 27 +++++++++++++++++++++++++-- package.json | 1 + test/deviceReceive.test.js | 27 +++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/lib/extension/deviceReceive.js b/lib/extension/deviceReceive.js index 9f04c4cf..a28f8144 100644 --- a/lib/extension/deviceReceive.js +++ b/lib/extension/deviceReceive.js @@ -1,6 +1,7 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); const utils = require('../util/utils'); +const debounce = require('debounce'); /** * This extensions handles messages received from devices. @@ -13,12 +14,28 @@ class DeviceReceive { this.publishEntityState = publishEntityState; this.coordinator = null; this.elapsed = {}; + this.debouncers = {}; } onZigbeeStarted() { this.coordinator = this.zigbee.getCoordinator().device.ieeeAddr; } + publishDebounce(ieeeAddr, payload, time) { + if (!this.debouncers[ieeeAddr]) { + this.debouncers[ieeeAddr] = { + payload: {}, + publish: debounce(() => { + this.publishEntityState(ieeeAddr, this.debouncers[ieeeAddr].payload); + this.debouncers[ieeeAddr].payload = {}; + }, time * 1000), + }; + } + + this.debouncers[ieeeAddr].payload = {...this.debouncers[ieeeAddr].payload, ...payload}; + this.debouncers[ieeeAddr].publish(); + } + onZigbeeMessage(message, device, mappedDevice) { if (message.type == 'devInterview') { if (!settings.getDevice(message.data)) { @@ -45,7 +62,8 @@ class DeviceReceive { } // Check if this is a new device. - if (!settings.getDevice(device.ieeeAddr)) { + const settingsDevice = settings.getDevice(device.ieeeAddr); + if (!settingsDevice) { logger.info(`New device '${device.modelId}' with address ${device.ieeeAddr} connected!`); settings.addDevice(device.ieeeAddr); this.mqtt.log('device_connected', device.ieeeAddr, {modelID: device.modelId}); @@ -141,7 +159,12 @@ class DeviceReceive { this.elapsed[device.ieeeAddr] = now; } - this.publishEntityState(device.ieeeAddr, payload); + // Check if we have to debounce + if (settingsDevice && settingsDevice.hasOwnProperty('debounce')) { + this.publishDebounce(device.ieeeAddr, payload, settingsDevice.debounce); + } else { + this.publishEntityState(device.ieeeAddr, payload); + } }; let payload = {}; diff --git a/package.json b/package.json index 2db0ef84..d48f0871 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://koenkk.github.io/zigbee2mqtt", "dependencies": { + "debounce": "*", "git-last-commit": "*", "js-yaml": "*", "mkdir-recursive": "*", diff --git a/test/deviceReceive.test.js b/test/deviceReceive.test.js index 4d9cd9f7..ef42f383 100644 --- a/test/deviceReceive.test.js +++ b/test/deviceReceive.test.js @@ -3,6 +3,8 @@ const settings = require('../lib/util/settings'); const devices = require('zigbee-shepherd-converters').devices; const utils = require('./utils'); +const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + // Devices const WXKG11LM = devices.find((d) => d.model === 'WXKG11LM'); const WXKG02LM = devices.find((d) => d.model === 'WXKG02LM'); @@ -63,6 +65,31 @@ describe('DeviceReceive', () => { expect(publishEntityState.mock.calls[0][1]).toStrictEqual({temperature: -0.85}); }); + it('Should debounce messages', async () => { + const device = {ieeeAddr: '0x12345678'}; + jest.spyOn(settings, 'getDevice').mockReturnValue({debounce: 0.1}); + const message1 = utils.zigbeeMessage( + device, 'msTemperatureMeasurement', 'attReport', {measuredValue: 8}, 1 + ); + const message2 = utils.zigbeeMessage( + device, 'msRelativeHumidity', 'attReport', {measuredValue: 1}, 1 + ); + const message3 = utils.zigbeeMessage( + device, 'msPressureMeasurement', 'attReport', {measuredValue: 2}, 1 + ); + deviceReceive.onZigbeeMessage(message1, device, WSDCGQ11LM); + deviceReceive.onZigbeeMessage(message2, device, WSDCGQ11LM); + deviceReceive.onZigbeeMessage(message3, device, WSDCGQ11LM); + await wait(200); + expect(publishEntityState).toHaveBeenCalledTimes(1); + expect(publishEntityState.mock.calls[0][1]).toStrictEqual({temperature: 0.08, humidity: 0.01, pressure: 2}); + + deviceReceive.onZigbeeMessage(message1, device, WSDCGQ11LM); + await wait(200); + expect(publishEntityState).toHaveBeenCalledTimes(2); + expect(publishEntityState.mock.calls[1][1]).toStrictEqual({temperature: 0.08}); + }); + it('Should handle a zigbee message with 1 precision', () => { const device = {ieeeAddr: '0x12345678'}; jest.spyOn(settings, 'getDevice').mockReturnValue({temperature_precision: 1});