From 0e67f8fa0ad847caf3618f3c674902e407cdb50f Mon Sep 17 00:00:00 2001 From: baze- Date: Sat, 4 Mar 2023 17:43:01 +0200 Subject: [PATCH] Fix old state send when long debounce time is used (#16828) * Fixed caching problem where old messages were sent out if long debounce values is used for sensor. * Fixed caching problem where old messages were sent out if long debounce values is used for sensor * State cache updated during debounce IF cache is enabled in configuration. * Fixed code styles * Implemented test case for testing State cache is updated correctly during debouncing * If-statement was obsolete as cache can be update right away. --------- Co-authored-by: Koen Kanters --- lib/extension/receive.ts | 7 +++++++ test/receive.test.js | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/lib/extension/receive.ts b/lib/extension/receive.ts index a5db2162d..5ca82df17 100755 --- a/lib/extension/receive.ts +++ b/lib/extension/receive.ts @@ -49,6 +49,13 @@ export default class Receive extends Extension { // extend debounced payload with current this.debouncers[device.ieeeAddr].payload = {...this.debouncers[device.ieeeAddr].payload, ...payload}; + + // Update state cache right away. This makes sure that during debouncing cached state is always up to date. + // ( Update right away as "lastSeenChanged" event might occur while debouncer is still active. + // And if that happens it would cause old message to be published from cache. + // By updating cache we make sure that state cache is always up-to-date. + this.state.set(device, this.debouncers[device.ieeeAddr].payload); + this.debouncers[device.ieeeAddr].publish(); } diff --git a/test/receive.test.js b/test/receive.test.js index 638acf4db..8e9b40bb9 100755 --- a/test/receive.test.js +++ b/test/receive.test.js @@ -178,6 +178,47 @@ describe('Receive', () => { expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({temperature: 0.07, pressure: 2, humidity: 0.03}); }); + it('Should NOT publish old messages from State cache during debouncing', async () => { + + // Summary: + // First send multiple measurements to device that is debouncing. Make sure only one message is sent out to MQTT. This also ensures first message is cached to "State". + // Then send another measurement to that same device and trigger asyncronous event to push data from Cache. Newest value should be sent out. + + const device = zigbeeHerdsman.devices.WSDCGQ11LM; + settings.set(['devices', device.ieeeAddr, 'debounce'], 0.1); + await zigbeeHerdsman.events.message({data: {measuredValue: 8}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); + await zigbeeHerdsman.events.message( {data: {measuredValue: 1}, cluster: 'msRelativeHumidity', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10} ); + await zigbeeHerdsman.events.message( {data: {measuredValue: 2}, cluster: 'msPressureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10} ); + await flushPromises(); + jest.advanceTimersByTime(50); + // Test that measurements are combined(=debounced) + expect(MQTT.publish).toHaveBeenCalledTimes(0); + jest.runOnlyPendingTimers(); + await flushPromises(); + + // Test that only one MQTT is sent out and test its values. + 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}); + + // Send another Zigbee messages... + await zigbeeHerdsman.events.message({data: {measuredValue: 9}, cluster: 'msTemperatureMeasurement', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10}); + const realDevice = controller.zigbee.resolveEntity(device); + + // Trigger asyncronous event while device is "debouncing" to trigger Message to be sent out from State cache. + await controller.publishEntityState( realDevice, {} ); + jest.runOnlyPendingTimers(); + await flushPromises(); + + // Total of 3 messages should have triggered. + expect(MQTT.publish).toHaveBeenCalledTimes(3); + + // Test that message pushed by asyncronous message contains NEW measurement and not old. + expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2}); + // Test that messages after debouncing contains NEW measurement and not old. + expect(JSON.parse(MQTT.publish.mock.calls[2][1])).toStrictEqual({temperature: 0.09, humidity: 0.01, pressure: 2}); + }); + it('Shouldnt republish old state', async () => { // https://github.com/Koenkk/zigbee2mqtt/issues/3572 const device = zigbeeHerdsman.devices.bulb;