From 5ac7d9893de817d39de3f689716ec3297df5aba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Hagelb=C3=A4ck?= Date: Mon, 1 Feb 2021 21:30:44 +0100 Subject: [PATCH] Break out main program flow to PlejdAddon class and add catching of init errors with slow retry --- plejd/PlejdAddon.js | 152 ++++++++++++++++++++++++++++++++++++++++++++ plejd/main.js | 141 +++------------------------------------- 2 files changed, 162 insertions(+), 131 deletions(-) create mode 100644 plejd/PlejdAddon.js diff --git a/plejd/PlejdAddon.js b/plejd/PlejdAddon.js new file mode 100644 index 0000000..761cb95 --- /dev/null +++ b/plejd/PlejdAddon.js @@ -0,0 +1,152 @@ +const EventEmitter = require('events'); + +const Configuration = require('./Configuration'); +const Logger = require('./Logger'); +const PlejdApi = require('./PlejdApi'); +const PlejdBLE = require('./PlejdBLE'); +const MqttClient = require('./MqttClient'); +const SceneManager = require('./SceneManager'); +const DeviceRegistry = require('./DeviceRegistry'); + +const logger = Logger.getLogger('plejd-main'); + +class PlejdAddon extends EventEmitter { + bleInitTimeout; + config; + deviceRegistry; + plejdApi; + plejdBLE; + mqttClient; + sceneManager; + + constructor() { + super(); + + this.config = Configuration.getOptions(); + this.deviceRegistry = new DeviceRegistry(); + + this.plejdApi = new PlejdApi(this.deviceRegistry); + this.plejdBLE = new PlejdBLE(this.deviceRegistry); + this.sceneManager = new SceneManager(this.deviceRegistry, this.plejdBle); + this.mqttClient = new MqttClient(this.deviceRegistry); + } + + async init() { + logger.info('Main Plejd addon init()...'); + + await this.plejdApi.init(); + this.sceneManager.init(); + + ['SIGINT', 'SIGHUP', 'SIGTERM'].forEach((signal) => { + process.on(signal, () => { + this.mqttClient.disconnect(() => process.exit(0)); + }); + }); + + this.mqttClient.on('connected', () => { + try { + logger.verbose('connected to mqtt.'); + this.mqttClient.sendDiscoveryToHomeAssistant(); + } catch (err) { + logger.error('Error in MqttClient.connected callback in main.js', err); + } + }); + + // subscribe to changes from HA + this.mqttClient.on('stateChanged', (device, command) => { + try { + const deviceId = device.id; + + if (device.typeName === 'Scene') { + // we're triggering a scene, lets do that and jump out. + // since scenes aren't "real" devices. + this.sceneManager.executeScene(device.id); + return; + } + + let state = 'OFF'; + let commandObj = {}; + + if (typeof command === 'string') { + // switch command + state = command; + commandObj = { + state, + }; + + // since the switch doesn't get any updates on whether it's on or not, + // we fake this by directly send the updateState back to HA in order for + // it to change state. + this.mqttClient.updateState(deviceId, { + state: state === 'ON' ? 1 : 0, + }); + } else { + // eslint-disable-next-line prefer-destructuring + state = command.state; + commandObj = command; + } + + if (state === 'ON') { + this.plejdBLE.turnOn(deviceId, commandObj); + } else { + this.plejdBLE.turnOff(deviceId, commandObj); + } + } catch (err) { + logger.error('Error in MqttClient.stateChanged callback in main.js', err); + } + }); + + this.mqttClient.init(); + + // init the BLE interface + this.plejdBLE.on('connectFailed', () => { + logger.verbose('Were unable to connect, will retry.'); + this._bleInitLoop(); + }); + + // this.plejdBLE.init(); + + this.plejdBLE.on('authenticated', () => { + logger.verbose('plejd: connected via bluetooth.'); + }); + + // subscribe to changes from Plejd + this.plejdBLE.on('stateChanged', (deviceId, command) => { + try { + this.mqttClient.updateState(deviceId, command); + } catch (err) { + logger.error('Error in PlejdService.stateChanged callback in main.js', err); + } + }); + + this.plejdBLE.on('sceneTriggered', (deviceId, sceneId) => { + try { + this.mqttClient.sceneTriggered(sceneId); + } catch (err) { + logger.error('Error in PlejdService.sceneTriggered callback in main.js', err); + } + }); + + await this._bleInitLoop(); + } + + async _bleInitLoop() { + try { + if (this.bleInitTimeout) { + clearTimeout(this.bleInitTimeout); + } + await this.plejdBLE.init(); + } catch (err) { + logger.warn('Failed BLE init, trying again in 35s', err); + this.bleInitTimer = setTimeout(() => { + try { + this._bleInitLoop(); + } catch (err2) { + logger.warn('Why do we need to catch error here?', err2); + } + }, 35000); + } + } +} + +module.exports = PlejdAddon; diff --git a/plejd/main.js b/plejd/main.js index 1136140..fa54005 100644 --- a/plejd/main.js +++ b/plejd/main.js @@ -1,144 +1,23 @@ -const PlejdApi = require('./PlejdApi'); -const MqttClient = require('./MqttClient'); - const Logger = require('./Logger'); -const PlejdService = require('./PlejdService'); -const SceneManager = require('./SceneManager'); -const Configuration = require('./Configuration'); +const PlejdAddon = require('./PlejdAddon'); const logger = Logger.getLogger('plejd-main'); const version = '0.5.1'; async function main() { - logger.info(`Starting Plejd add-on v. ${version}`); + try { + logger.info(`Starting Plejd add-on v. ${version}`); - const config = Configuration.getConfiguration(); + const addon = new PlejdAddon(); - if (!config.connectionTimeout) { - config.connectionTimeout = 2; + await addon.init(); + + logger.info('main() finished'); + } catch (err) { + logger.error('Catastrophic error. Resetting entire addon in 1 minute', err); + setTimeout(() => main(), 60000); } - - const plejdApi = new PlejdApi( - config.site, - config.username, - config.password, - config.includeRoomsAsLights, - ); - const client = new MqttClient(config.mqttBroker, config.mqttUsername, config.mqttPassword); - - ['SIGINT', 'SIGHUP', 'SIGTERM'].forEach((signal) => { - process.on(signal, () => { - client.disconnect(() => process.exit(0)); - }); - }); - - plejdApi.login().then(() => { - // load all sites and find the one that we want (from config) - plejdApi.getSites().then((site) => { - // load the site and retrieve the crypto key - plejdApi.getSite(site.site.siteId).then((cryptoKey) => { - // parse all devices from the API - const devices = plejdApi.getDevices(); - - client.on('connected', () => { - try { - logger.verbose('connected to mqtt.'); - client.discover(devices); - } catch (err) { - logger.error('Error in MqttClient.connected callback in main.js', err); - } - }); - - client.init(); - - // init the BLE interface - const sceneManager = new SceneManager(plejdApi.site, devices); - const plejd = new PlejdService( - cryptoKey, - devices, - sceneManager, - config.connectionTimeout, - config.writeQueueWaitTime, - ); - plejd.on('connectFailed', () => { - logger.verbose('Were unable to connect, will retry connection in 10 seconds.'); - setTimeout(() => { - plejd - .init() - .catch((e) => logger.error('Error in init() from connectFailed in main.js', e)); - }, 10000); - }); - - plejd.init(); - - plejd.on('authenticated', () => { - logger.verbose('plejd: connected via bluetooth.'); - }); - - // subscribe to changes from Plejd - plejd.on('stateChanged', (deviceId, command) => { - try { - client.updateState(deviceId, command); - } catch (err) { - logger.error('Error in PlejdService.stateChanged callback in main.js', err); - } - }); - - plejd.on('sceneTriggered', (deviceId, scene) => { - try { - client.sceneTriggered(scene); - } catch (err) { - logger.error('Error in PlejdService.sceneTriggered callback in main.js', err); - } - }); - - // subscribe to changes from HA - client.on('stateChanged', (device, command) => { - try { - const deviceId = device.id; - - if (device.typeName === 'Scene') { - // we're triggering a scene, lets do that and jump out. - // since scenes aren't "real" devices. - plejd.triggerScene(device.id); - return; - } - - let state = 'OFF'; - let commandObj = {}; - - if (typeof command === 'string') { - // switch command - state = command; - commandObj = { - state, - }; - - // since the switch doesn't get any updates on whether it's on or not, - // we fake this by directly send the updateState back to HA in order for - // it to change state. - client.updateState(deviceId, { - state: state === 'ON' ? 1 : 0, - }); - } else { - // eslint-disable-next-line prefer-destructuring - state = command.state; - commandObj = command; - } - - if (state === 'ON') { - plejd.turnOn(deviceId, commandObj); - } else { - plejd.turnOff(deviceId, commandObj); - } - } catch (err) { - logger.error('Error in MqttClient.stateChanged callback in main.js', err); - } - }); - }); - }); - }); } main();