From 04b2f24d6b2d811ad31bb0b23d11f73e7067b391 Mon Sep 17 00:00:00 2001 From: Koenkk Date: Sun, 5 Aug 2018 18:38:33 +0200 Subject: [PATCH] Persist devices state across restarts. #249 --- .gitignore | 3 ++- lib/controller.js | 17 +++++++++------ lib/state.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 lib/state.js diff --git a/.gitignore b/.gitignore index 6aea616b8..5b3eb58d8 100644 --- a/.gitignore +++ b/.gitignore @@ -60,4 +60,5 @@ typings/ # data data/database.db data/config.json -data/log*.txt \ No newline at end of file +data/log*.txt +data/state.json \ No newline at end of file diff --git a/lib/controller.js b/lib/controller.js index 96154f499..c98cd24a1 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -1,5 +1,6 @@ const MQTT = require('./mqtt'); const Zigbee = require('./zigbee'); +const State = require('./state'); const logger = require('./util/logger'); const settings = require('./util/settings'); const zigbeeShepherdConverters = require('zigbee-shepherd-converters'); @@ -32,7 +33,7 @@ class Controller { constructor() { this.zigbee = new Zigbee(); this.mqtt = new MQTT(); - this.stateCache = {}; + this.state = new State(); this.configured = []; this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this); this.handleMQTTMessage = this.handleMQTTMessage.bind(this); @@ -99,8 +100,8 @@ class Controller { sendAllCachedStates() { this.zigbee.getAllClients().forEach((device) => { - if (this.stateCache.hasOwnProperty(device.ieeeAddr)) { - this.mqttPublishDeviceState(device.ieeeAddr, this.stateCache[device.ieeeAddr], false); + if (this.state.exists(device.ieeeAddr)) { + this.mqttPublishDeviceState(device.ieeeAddr, this.state.get(device.ieeeAddr), false); } }); } @@ -152,6 +153,7 @@ class Controller { } stop(callback) { + this.state.save(); this.mqtt.disconnect(); this.pollTimer(false); this.softResetTimeout(false); @@ -393,6 +395,9 @@ class Controller { // Remove from configuration.yaml settings.removeDevice(deviceID); + // Remove from state + this.state.remove(deviceID); + logger.info(`Successfully removed ${deviceID}`); this.mqtt.log('device_removed', message); }; @@ -549,13 +554,13 @@ class Controller { mqttPublishDeviceState(deviceID, payload, cache) { if (cacheState) { // Add cached state to payload - if (this.stateCache[deviceID]) { - payload = objectAssignDeep.noMutate(this.stateCache[deviceID], payload); + if (this.state.exists(deviceID)) { + payload = objectAssignDeep.noMutate(this.state.get(deviceID), payload); } // Update state cache with new state. if (cache) { - this.stateCache[deviceID] = payload; + this.state.set(deviceID, payload); } } diff --git a/lib/state.js b/lib/state.js new file mode 100644 index 000000000..153bccb2b --- /dev/null +++ b/lib/state.js @@ -0,0 +1,55 @@ +const logger = require('./util/logger'); +const data = require('./util/data'); +const fs = require('fs'); + +const saveInterval = 1000 * 60 * 5; // 5 minutes + +class State { + constructor() { + this.state = {}; + this.file = data.joinPath('state.json'); + this._load(); + + // Save the state on every interval + this.timer = setInterval(() => this.save(), saveInterval); + } + + _load() { + if (fs.existsSync(this.file)) { + try { + this.state = JSON.parse(fs.readFileSync(this.file, 'utf8')); + logger.debug(`Loaded state from file ${this.file}`); + } catch (e) { + logger.debug(`Failed to load state from file ${this.file} (corrupt file?)`); + } + } else { + logger.debug(`Can't load state from file ${this.file} (doesn't exsist)`); + } + } + + save() { + logger.debug(`Saving state to file ${this.file}`); + const json = JSON.stringify(this.state, null, 4); + fs.writeFileSync(this.file, json, 'utf8'); + } + + exists(ID) { + return this.state.hasOwnProperty(ID); + } + + get(ID) { + return this.state[ID]; + } + + set(ID, state) { + this.state[ID] = state; + } + + remove(ID) { + if (this.exists(ID)) { + delete this.state[ID]; + } + } +} + +module.exports = State;