Break out main program flow to PlejdAddon class and add catching of init errors with slow retry

This commit is contained in:
Victor Hagelbäck 2021-02-01 21:30:44 +01:00
parent 62a6359544
commit 5ac7d9893d
2 changed files with 162 additions and 131 deletions

152
plejd/PlejdAddon.js Normal file
View file

@ -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;

View file

@ -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();