From 19bd87de4a8b427352e72d8de0a53e2184855a0c Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 31 Dec 2022 09:15:11 +0000 Subject: [PATCH] Keep cached device state until Zigbee2MQTT is stopped (#15770) * Fix extension path to make debugginge easier in VScode * Fix linting error * Add setting "cache_state_persist_on_leave" (default false) This setting does not remove a device from State if it leaves the network * Merge from upstream kk/dev * Re-define cache_state_persist_on_leave to be a timeout Allows the config to specify how long the state should be held (and the device to re-join & maintain the state). Remove all pending deletions if the system is stopped * Add debug. Fix "time" test * Disable esline warnings for long debug lines * Simplify state maintenance for leave/join devices * Restore default configuration.yaml * Don't track device presence via events, but examine herdsman on system stop * Update configuration.yaml * Update state.ts * Update state.ts * Update controller.test.js * Update eventBus.ts * Update eventBus.ts Co-authored-by: Koen Kanters --- lib/controller.ts | 2 +- lib/state.ts | 11 +++++++---- test/controller.test.js | 9 +++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/controller.ts b/lib/controller.ts index 6f6ab05cb..9099c2a1c 100644 --- a/lib/controller.ts +++ b/lib/controller.ts @@ -57,7 +57,7 @@ export class Controller { }); this.zigbee = new Zigbee(this.eventBus); this.mqtt = new MQTT(this.eventBus); - this.state = new State(this.eventBus); + this.state = new State(this.eventBus, this.zigbee); this.restartCallback = restartCallback; this.exitCallback = exitCallback; diff --git a/lib/state.ts b/lib/state.ts index 676fa39f4..48e9128cd 100644 --- a/lib/state.ts +++ b/lib/state.ts @@ -17,10 +17,10 @@ class State { private state: {[s: string | number]: KeyValue} = {}; private file = data.joinPath('state.json'); private timer: NodeJS.Timer = null; - private eventBus: EventBus; - constructor(eventBus: EventBus) { + constructor(private readonly eventBus: EventBus, private readonly zigbee: Zigbee) { this.eventBus = eventBus; + this.zigbee = zigbee; } start(): void { @@ -28,11 +28,14 @@ class State { // Save the state on every interval this.timer = setInterval(() => this.save(), saveInterval); - this.eventBus.onDeviceLeave(this, (data) => delete this.state[data.ieeeAddr]); } stop(): void { - this.eventBus.removeListeners(this); + // Remove any invalid states (ie when the device has left the network) when the system is stopped + Object.keys(this.state) + .filter((k) => typeof k === 'string' && !this.zigbee.resolveEntity(k)) // string key = ieeeAddr + .forEach((k) => delete this.state[k]); + clearTimeout(this.timer); this.save(); } diff --git a/test/controller.test.js b/test/controller.test.js index 8a18982e3..7d793889e 100644 --- a/test/controller.test.js +++ b/test/controller.test.js @@ -719,4 +719,13 @@ describe('Controller', () => { await flushPromises(); expect(logger.debug).toHaveBeenCalledWith(`Received Zigbee message from 'Coordinator', type 'attributeReport', cluster 'genBasic', data '{"modelId":null}' from endpoint 1, ignoring since it is from coordinator`); }); + + it('Should remove state of removed device when stopped', async () => { + await controller.start(); + const device = controller.zigbee.resolveEntity('bulb'); + expect(controller.state.state[device.ieeeAddr]).toStrictEqual({"brightness":50,"color_temp":370,"linkquality":99,"state":"ON"}); + device.zh.isDeleted = true; + await controller.stop(); + expect(controller.state.state[device.ieeeAddr]).toStrictEqual(undefined); + }); });