Improve logging across all js files

- Based on "winston" logging library
- Removed no longer needed lodash
- Locked npm dependencies to most recent major versions to avoid installs breaking due to node module updates
This commit is contained in:
Victor Hagelbäck 2021-01-21 21:31:37 +01:00
parent ba27dd6d18
commit 4176cfb714
9 changed files with 434 additions and 214 deletions

View file

@ -8,11 +8,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Copy data for add-on
COPY ./api.js /plejd/
COPY ./ble.bluez.js /plejd/
COPY ./config.json /plejd/
COPY ./Logger.js /plejd/
COPY ./main.js /plejd/
COPY ./mqtt.js /plejd/
COPY ./package.json /plejd/
COPY ./ble.bluez.js /plejd/
COPY ./scene.manager.js /plejd/
ARG BUILD_ARCH

99
plejd/Logger.js Normal file
View file

@ -0,0 +1,99 @@
const winston = require("winston");
const { colorize, combine, label, printf, timestamp } = winston.format;
const logFormat = printf(info => {
if(info.stack) {
return `${info.timestamp} ${info.level} [${info.label}] ${info.message}\n${info.stack}`;
}
return `${info.timestamp} ${info.level} [${info.label}] ${info.message}`;
});
/** Winston-based logger */
class Logger {
constructor () {
throw new Error("Please call createLogger instead");
}
/** Created logger will follow Winston createLogger, but
* - add module name to logger
* - swap debug/verbose levels and omit http to mimic HA standard
* Levels (in order): error, warn, info, debug, verbose, silly
* */
static getLogger(moduleName, level="verbose") {
const logger = winston.createLogger({
format: combine(
winston.format(info => {
switch (info.level) {
case "verbose":
info.level = "VRB";
break;
case "debug":
info.level = "DBG";
break;
default:
info.level = info.level.substring(0,3).toUpperCase()
}
return info;
})(),
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
label({ label: moduleName}),
colorize(),
logFormat,
),
level: level,
levels: Logger.logLevels().levels,
transports: [
new winston.transports.Console(),
]
});
winston.addColors(Logger.logLevels().colors);
return logger;
}
static logLevels() {
// Default (npm) levels
// levels = {
// error: 0,
// warn: 1,
// info: 2,
// http: 3,
// verbose: 4,
// debug: 5,
// silly: 6
// }
// colors = {
// error: 'red',
// warn: 'yellow',
// info: 'green',
// http: 'green',
// verbose: 'cyan',
// debug: 'blue',
// silly: 'magenta'
// };
// Mimic HA standard below
// Debug/verbose swapped compared to npm levels, http omitted
return {
levels: {
error: 0,
warn: 1,
info: 2,
debug: 3,
verbose: 4,
silly: 6
},
colors: {
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'cyan',
verbose: 'blue',
silly: 'magenta'
}
};
}
}
module.exports = Logger;

View file

@ -1,5 +1,6 @@
const axios = require('axios');
const EventEmitter = require('events');
const Logger = require('./Logger');
API_APP_ID = 'zHtVqXt8k4yFyk2QGmgp48D9xZr2G94xWYnF4dak';
API_BASE_URL = 'https://cloud.plejd.com/parse/';
@ -7,20 +8,7 @@ API_LOGIN_URL = 'login';
API_SITE_LIST_URL = 'functions/getSiteList';
API_SITE_DETAILS_URL = 'functions/getSiteById';
const logInfo = true; // Normal operations
const logDebug = false; // Chatty
const logVerbose = false; // Very chatty
const consoleLogger = (level) => (...msg) =>
console.log(new Date().toISOString().replace("T", " ").substring(0, 19) + "Z", level, "plejd-api", ...msg);
const getLogger = (level, shouldLog) => (shouldLog ? consoleLogger(level) : () => {});
const errLogger = getLogger("ERR", true);
const infLogger = getLogger("INF", logInfo);
const dbgLogger = getLogger("DBG", logDebug);
const vrbLogger = getLogger("vrb", logVerbose);
const logger = Logger.getLogger("plejd-api");
class PlejdApi extends EventEmitter {
constructor(siteName, username, password, includeRoomsAsLights) {
@ -35,13 +23,9 @@ class PlejdApi extends EventEmitter {
this.site = null;
}
updateSettings(settings) {
logVerbose("Got new settings: ", settings);
}
login() {
infLogger('login()');
infLogger('logging into ' + this.siteName);
logger.info('login()');
logger.info('logging into ' + this.siteName);
const self = this;
const instance = axios.create({
@ -53,7 +37,7 @@ class PlejdApi extends EventEmitter {
});
return new Promise((resolve, reject) => {
dbgLogger('sending POST to ' + API_BASE_URL + API_LOGIN_URL);
logger.debug('sending POST to ' + API_BASE_URL + API_LOGIN_URL);
instance.post(
API_LOGIN_URL,
@ -62,11 +46,11 @@ class PlejdApi extends EventEmitter {
'password': this.password
})
.then((response) => {
infLogger('got session token response');
logger.info('got session token response');
self.sessionToken = response.data.sessionToken;
if (!self.sessionToken) {
errLogger('No session token received');
logger.error('No session token received');
reject('no session token received.');
}
@ -74,10 +58,10 @@ class PlejdApi extends EventEmitter {
})
.catch((error) => {
if (error.response.status === 400) {
errLogger('Server returned status 400. probably invalid credentials, please verify.');
logger.error('Server returned status 400. probably invalid credentials, please verify.');
}
else {
errLogger('Unable to retrieve session token response: ' + error);
logger.error('Unable to retrieve session token response: ', error);
}
reject('unable to retrieve session token response: ' + error);
@ -86,7 +70,7 @@ class PlejdApi extends EventEmitter {
}
getSites() {
infLogger('getSites()');
logger.info('Get all Plejd sites for account...');
const self = this;
const instance = axios.create({
@ -99,15 +83,15 @@ class PlejdApi extends EventEmitter {
});
return new Promise((resolve, reject) => {
dbgLogger('sending POST to ' + API_BASE_URL + API_SITE_LIST_URL);
logger.debug('sending POST to ' + API_BASE_URL + API_SITE_LIST_URL);
instance.post(API_SITE_LIST_URL)
.then((response) => {
infLogger('got site list response');
logger.info('got site list response');
const site = response.data.result.find(x => x.site.title == self.siteName);
if (!site) {
errLogger('error: failed to find a site named ' + self.siteName);
logger.error('error: failed to find a site named ' + self.siteName);
reject('failed to find a site named ' + self.siteName);
return;
}
@ -115,14 +99,14 @@ class PlejdApi extends EventEmitter {
resolve(site);
})
.catch((error) => {
errLogger('error: unable to retrieve list of sites. error: ' + error);
logger.error('error: unable to retrieve list of sites. error: ', error);
return reject('plejd-api: unable to retrieve list of sites. error: ' + error);
});
});
}
getSite(siteId) {
infLogger('getSite(...)');
logger.info(`Get site details...`);
const self = this;
const instance = axios.create({
@ -135,14 +119,14 @@ class PlejdApi extends EventEmitter {
});
return new Promise((resolve, reject) => {
dbgLogger('sending POST to ' + API_BASE_URL + API_SITE_DETAILS_URL);
logger.debug('sending POST to ' + API_BASE_URL + API_SITE_DETAILS_URL);
instance.post(API_SITE_DETAILS_URL, { siteId: siteId })
.then((response) => {
infLogger('got site details response');
logger.info('got site details response');
if (response.data.result.length === 0) {
const msg = 'no site with ID ' + siteId + ' was found.';
errLogger('error: ' + msg);
logger.error('error: ' + msg);
reject(msg);
return;
}
@ -153,7 +137,7 @@ class PlejdApi extends EventEmitter {
resolve(self.cryptoKey);
})
.catch((error) => {
errLogger('error: unable to retrieve the crypto key. error: ' + error);
logger.error('error: unable to retrieve the crypto key. error: ', error);
return reject('plejd-api: unable to retrieve the crypto key. error: ' + error);
});
});
@ -162,7 +146,7 @@ class PlejdApi extends EventEmitter {
getDevices() {
let devices = [];
vrbLogger(JSON.stringify(this.site));
logger.verbose(JSON.stringify(this.site));
const roomDevices = {};
@ -252,7 +236,7 @@ class PlejdApi extends EventEmitter {
}
if (this.includeRoomsAsLights) {
dbgLogger('includeRoomsAsLights is set to true, adding rooms too.');
logger.debug('includeRoomsAsLights is set to true, adding rooms too.');
for (let i = 0; i < this.site.rooms.length; i++) {
const room = this.site.rooms[i];
const roomId = room.roomId;
@ -268,7 +252,7 @@ class PlejdApi extends EventEmitter {
devices.push(newDevice);
}
dbgLogger('includeRoomsAsLights done.');
logger.debug('includeRoomsAsLights done.');
}
// add scenes as switches

View file

@ -2,20 +2,9 @@ const dbus = require('dbus-next');
const crypto = require('crypto');
const xor = require('buffer-xor');
const EventEmitter = require('events');
const Logger = require('./Logger');
const logInfo = true; // Normal operations
const logDebug = false; // Chatty
const logVerbose = false; // Very chatty
const consoleLogger = (level) => (...msg) =>
console.log(new Date().toISOString().replace('T', ' ').substring(0, 19) + 'Z', level, 'plejd-ble', ...msg);
const getLogger = (level, shouldLog) => (shouldLog ? consoleLogger(level) : () => {});
const errLogger = getLogger('ERR', true);
const infLogger = getLogger('INF', logInfo);
const dbgLogger = getLogger('DBG', logDebug);
const vrbLogger = getLogger('vrb', logVerbose);
const logger = Logger.getLogger("plejd-ble");
// UUIDs
@ -46,7 +35,7 @@ class PlejdService extends EventEmitter {
constructor(cryptoKey, devices, sceneManager, connectionTimeout, writeQueueWaitTime, keepAlive = false) {
super();
infLogger('Starting Plejd BLE, resetting all device states.');
logger.info('Starting Plejd BLE, resetting all device states.');
this.cryptoKey = Buffer.from(cryptoKey.replace(/-/g, ''), 'hex');
@ -76,7 +65,7 @@ class PlejdService extends EventEmitter {
this.bus = dbus.systemBus();
this.adapter = null;
dbgLogger('wiring events and waiting for BLE interface to power up.');
logger.debug('wiring events and waiting for BLE interface to power up.');
this.wireEvents();
}
@ -98,7 +87,7 @@ class PlejdService extends EventEmitter {
clearInterval(this.pingRef);
clearTimeout(this.writeQueueRef);
infLogger('init()');
logger.info('init()');
const bluez = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, '/');
this.objectManager = await bluez.getInterface(DBUS_OM_INTERFACE);
@ -112,7 +101,7 @@ class PlejdService extends EventEmitter {
}
if (!this.adapter) {
errLogger('unable to find a bluetooth adapter that is compatible.');
logger.error('unable to find a bluetooth adapter that is compatible.');
return;
}
@ -126,7 +115,7 @@ class PlejdService extends EventEmitter {
const connected = managedObjects[path][BLUEZ_DEVICE_ID].Connected.value;
if (connected) {
infLogger('disconnecting ' + path);
logger.info('disconnecting ' + path);
await device.Disconnect();
}
@ -144,22 +133,22 @@ class PlejdService extends EventEmitter {
try {
await this.adapter.StartDiscovery();
} catch (err) {
errLogger('failed to start discovery. Make sure no other add-on is currently scanning.');
logger.error('failed to start discovery. Make sure no other add-on is currently scanning.');
return;
}
return new Promise(resolve =>
setTimeout(() => resolve(
this._internalInit().catch((err) => { errLogger('InternalInit exception! Will rethrow.', err); throw err; })
this._internalInit().catch((err) => { logger.error('InternalInit exception! Will rethrow.', err); throw err; })
), this.connectionTimeout * 1000
)
);
}
async _internalInit() {
dbgLogger(`Got ${this.bleDevices.length} device(s).`);
logger.debug(`Got ${this.bleDevices.length} device(s).`);
for (const plejd of this.bleDevices) {
dbgLogger(`Inspecting ${plejd['path']}`);
logger.debug(`Inspecting ${plejd['path']}`);
try {
const proxyObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, plejd['path']);
@ -174,9 +163,9 @@ class PlejdService extends EventEmitter {
fixedPlejdPath = fixedPlejdPath.replace(/_/g, '');
plejd['device'] = this.devices.find(x => x.serialNumber === fixedPlejdPath);
dbgLogger(`Discovered ${plejd['path']} with rssi ${plejd['rssi']}`);
logger.debug(`Discovered ${plejd['path']} with rssi ${plejd['rssi']}`);
} catch (err) {
errLogger(`Failed inspecting ${plejd['path']}. `, err);
logger.error(`Failed inspecting ${plejd['path']}. `, err);
}
}
@ -186,13 +175,13 @@ class PlejdService extends EventEmitter {
for (const plejd of sortedDevices) {
try {
if (plejd['instance']) {
infLogger(`Connecting to ${plejd['path']}`);
logger.info(`Connecting to ${plejd['path']}`);
await plejd['instance'].Connect();
connectedDevice = plejd;
break
}
} catch (err) {
errLogger('Warning: unable to connect, will retry. ', err);
logger.error('Warning: unable to connect, will retry. ', err);
}
}
@ -208,12 +197,12 @@ class PlejdService extends EventEmitter {
for (let path of managedPaths) {
const pathInterfaces = Object.keys(managedObjects[path]);
if (pathInterfaces.indexOf(iface) > -1) {
dbgLogger(`Found BLE interface '${iface}' at ${path}`);
logger.debug(`Found BLE interface '${iface}' at ${path}`);
try {
const adapterObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, path);
return [path, adapterObject.getInterface(iface), adapterObject];
} catch (err) {
errLogger(`Failed to get interface '${iface}'. `, err);
logger.error(`Failed to get interface '${iface}'. `, err);
}
}
}
@ -227,34 +216,25 @@ class PlejdService extends EventEmitter {
if (interfaceKeys.indexOf(BLUEZ_DEVICE_ID) > -1) {
if (interfaces[BLUEZ_DEVICE_ID]['UUIDs'].value.indexOf(PLEJD_SERVICE) > -1) {
dbgLogger(`Found Plejd service on ${path}`);
logger.debug(`Found Plejd service on ${path}`);
this.bleDevices.push({
'path': path
});
} else {
errLogger('Uh oh, no Plejd device!');
logger.error('Uh oh, no Plejd device!');
}
}
}
updateSettings(settings) {
dbgLogger('Got new settings: ', settings);
if (settings.debug) {
debug = true;
} else {
debug = false;
}
}
turnOn(deviceId, command) {
const deviceName = (logVerbose || logDebug) ? this._getDeviceName(deviceId) : '';
infLogger(`Plejd got turn on command for ${deviceName} (${deviceId}), brightness ${command.brightness}${command.transition ? `, transition: ${command.transition}` : ''}`);
const deviceName = this._getDeviceName(deviceId);
logger.info(`Plejd got turn on command for ${deviceName} (${deviceId}), brightness ${command.brightness}${command.transition ? `, transition: ${command.transition}` : ''}`);
this._transitionTo(deviceId, command.brightness, command.transition, deviceName);
}
turnOff(deviceId, command) {
const deviceName = (logVerbose || logDebug) ? this._getDeviceName(deviceId) : '';
infLogger(`Plejd got turn off command for ${deviceName} (${deviceId}), brightness ${command.brightness}${command.transition ? `, transition: ${command.transition}` : ''}`);
const deviceName = this._getDeviceName(deviceId);
logger.info(`Plejd got turn off command for ${deviceName} (${deviceId}), brightness ${command.brightness}${command.transition ? `, transition: ${command.transition}` : ''}`);
this._transitionTo(deviceId, 0, command.transition, deviceName);
}
@ -281,8 +261,8 @@ class PlejdService extends EventEmitter {
const transitionSteps = Math.min(Math.abs(deltaBrightness), MAX_TRANSITION_STEPS_PER_SECOND * transition);
const transitionInterval = transition * 1000 / transitionSteps;
dbgLogger(`transitioning from ${initialBrightness} to ${targetBrightness} ${transition ? 'in ' + transition + ' seconds' : ''}.`);
vrbLogger(`delta brightness ${deltaBrightness}, steps ${transitionSteps}, interval ${transitionInterval} ms`);
logger.debug(`transitioning from ${initialBrightness} to ${targetBrightness} ${transition ? 'in ' + transition + ' seconds' : ''}.`);
logger.verbose(`delta brightness ${deltaBrightness}, steps ${transitionSteps}, interval ${transitionInterval} ms`);
const dtStart = new Date();
@ -302,11 +282,11 @@ class PlejdService extends EventEmitter {
nSteps++;
this._clearDeviceTransitionTimer(deviceId);
newBrightness = targetBrightness;
dbgLogger(`Queueing finalize ${deviceName} (${deviceId}) transition from ${initialBrightness} to ${targetBrightness} in ${tElapsedMs}ms. Done steps ${nSteps}. Average interval ${tElapsedMs / (nSteps || 1)} ms.`);
logger.debug(`Queueing finalize ${deviceName} (${deviceId}) transition from ${initialBrightness} to ${targetBrightness} in ${tElapsedMs}ms. Done steps ${nSteps}. Average interval ${tElapsedMs / (nSteps || 1)} ms.`);
this._setBrightness(deviceId, newBrightness, true, deviceName);
} else {
nSteps++;
vrbLogger(`Queueing dim transition for ${deviceName} (${deviceId}) to ${newBrightness}. Total queue length ${this.writeQueue.length}`);
logger.verbose(`Queueing dim transition for ${deviceName} (${deviceId}) to ${newBrightness}. Total queue length ${this.writeQueue.length}`);
this._setBrightness(deviceId, newBrightness, false, deviceName);
}
@ -314,7 +294,7 @@ class PlejdService extends EventEmitter {
}
else {
if (transition && isDimmable) {
dbgLogger(`Could not transition light change. Either initial value is unknown or change is too small. Requested from ${initialBrightness} to ${targetBrightness}`)
logger.debug(`Could not transition light change. Either initial value is unknown or change is too small. Requested from ${initialBrightness} to ${targetBrightness}`)
}
this._setBrightness(deviceId, targetBrightness, true, deviceName);
}
@ -325,13 +305,13 @@ class PlejdService extends EventEmitter {
let log = '';
if (!brightness && brightness !== 0) {
dbgLogger(`Queueing turn on ${deviceName} (${deviceId}). No brightness specified, setting DIM to previous.`);
logger.debug(`Queueing turn on ${deviceName} (${deviceId}). No brightness specified, setting DIM to previous.`);
payload = Buffer.from((deviceId).toString(16).padStart(2, '0') + '0110009701', 'hex');
log = 'ON';
}
else {
if (brightness <= 0) {
dbgLogger(`Queueing turn off ${deviceId}`);
logger.debug(`Queueing turn off ${deviceId}`);
payload = Buffer.from((deviceId).toString(16).padStart(2, '0') + '0110009700', 'hex');
log = 'OFF';
}
@ -340,7 +320,7 @@ class PlejdService extends EventEmitter {
brightness = 255;
}
dbgLogger(`Queueing ${deviceId} set brightness to ${brightness}`);
logger.debug(`Queueing ${deviceId} set brightness to ${brightness}`);
const brightnessVal = (brightness << 8) | brightness;
payload = Buffer.from((deviceId).toString(16).padStart(2, '0') + '0110009801' + (brightnessVal).toString(16).padStart(4, '0'), 'hex');
log = `DIM ${brightness}`;
@ -351,23 +331,23 @@ class PlejdService extends EventEmitter {
triggerScene(sceneIndex) {
const sceneName = this._getDeviceName(sceneIndex);
infLogger(`Triggering scene ${sceneName} (${sceneIndex}). Scene name might be misleading if there is a device with the same numeric id.`);
logger.info(`Triggering scene ${sceneName} (${sceneIndex}). Scene name might be misleading if there is a device with the same numeric id.`);
this.sceneManager.executeScene(sceneIndex, this);
}
async authenticate() {
infLogger('authenticate()');
logger.info('authenticate()');
try {
dbgLogger('Sending challenge to device');
logger.debug('Sending challenge to device');
await this.characteristics.auth.WriteValue([0], {});
dbgLogger('Reading response from device');
logger.debug('Reading response from device');
const challenge = await this.characteristics.auth.ReadValue({});
const response = this._createChallengeResponse(this.cryptoKey, Buffer.from(challenge));
dbgLogger('Responding to authenticate');
logger.debug('Responding to authenticate');
await this.characteristics.auth.WriteValue([...response], {});
} catch (err) {
errLogger('Failed to authenticate: ', err);
logger.error('Failed to authenticate: ', err);
}
// auth done, start ping
@ -382,11 +362,11 @@ class PlejdService extends EventEmitter {
async throttledInit(delay) {
if(this.initInProgress){
dbgLogger('ThrottledInit already in progress. Skipping this call and returning existing promise.')
logger.debug('ThrottledInit already in progress. Skipping this call and returning existing promise.')
return this.initInProgress;
}
this.initInProgress = new Promise((resolve) => setTimeout(async () => {
const result = await this.init().catch((err) => { errLogger('TrottledInit exception calling init(). Will re-throw.', err); throw err; });
const result = await this.init().catch((err) => { logger.error('TrottledInit exception calling init(). Will re-throw.', err); throw err; });
this.initInProgress = null;
resolve(result)
}, delay))
@ -395,20 +375,20 @@ class PlejdService extends EventEmitter {
async write(data) {
if (!data || !this.plejdService || !this.characteristics.data) {
dbgLogger('data, plejdService or characteristics not available. Cannot write()');
logger.debug('data, plejdService or characteristics not available. Cannot write()');
return;
}
try {
vrbLogger(`Sending ${data.length} byte(s) of data to Plejd`, data);
logger.verbose(`Sending ${data.length} byte(s) of data to Plejd`, data);
const encryptedData = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, data);
await this.characteristics.data.WriteValue([...encryptedData], {});
return true;
} catch (err) {
if (err.message === 'In Progress') {
dbgLogger('Write failed due to \'In progress\' ', err);
logger.debug('Write failed due to \'In progress\' ', err);
} else {
dbgLogger('Write failed ', err);
logger.debug('Write failed ', err);
}
await this.throttledInit(this.connectionTimeout * 1000);
return false;
@ -416,29 +396,29 @@ class PlejdService extends EventEmitter {
}
startPing() {
infLogger('startPing()');
logger.info('startPing()');
clearInterval(this.pingRef);
this.pingRef = setInterval(async () => {
vrbLogger('ping');
logger.silly('ping');
await this.ping();
}, 3000);
}
onPingSuccess(nr) {
vrbLogger('pong: ' + nr);
logger.silly('pong: ' + nr);
}
async onPingFailed(error) {
dbgLogger('onPingFailed(' + error + ')');
infLogger('ping failed, reconnecting.');
logger.debug('onPingFailed(' + error + ')');
logger.info('ping failed, reconnecting.');
clearInterval(this.pingRef);
await this.init();
}
async ping() {
vrbLogger('ping()');
logger.silly('ping()');
var ping = crypto.randomBytes(1);
let pong = null;
@ -447,13 +427,13 @@ class PlejdService extends EventEmitter {
await this.characteristics.ping.WriteValue([...ping], {});
pong = await this.characteristics.ping.ReadValue({});
} catch (err) {
errLogger('writing to plejd: ', err);
logger.error('Error writing to plejd: ', err);
this.emit('pingFailed', 'write error');
return;
}
if (((ping[0] + 1) & 0xff) !== pong[0]) {
errLogger('plejd ping failed');
logger.error('Plejd ping failed');
this.emit('pingFailed', 'plejd ping failed ' + ping[0] + ' - ' + pong[0]);
return;
}
@ -462,7 +442,7 @@ class PlejdService extends EventEmitter {
}
startWriteQueue() {
infLogger('startWriteQueue()');
logger.info('startWriteQueue()');
clearTimeout(this.writeQueueRef);
this.writeQueueRef = setTimeout(() => this.runWriteQueue(), this.writeQueueWaitTime);
@ -473,22 +453,22 @@ class PlejdService extends EventEmitter {
while (this.writeQueue.length > 0) {
const queueItem = this.writeQueue.pop();
const deviceName = this._getDeviceName(queueItem.deviceId);
dbgLogger(`Write queue: Processing ${deviceName} (${queueItem.deviceId}). Command ${queueItem.log}. Total queue length: ${this.writeQueue.length}`);
logger.debug(`Write queue: Processing ${deviceName} (${queueItem.deviceId}). Command ${queueItem.log}. Total queue length: ${this.writeQueue.length}`);
if (this.writeQueue.some((item) => item.deviceId === queueItem.deviceId)) {
vrbLogger(`Skipping ${deviceName} (${queueItem.deviceId}) ${queueItem.log} due to more recent command in queue.`);
logger.verbose(`Skipping ${deviceName} (${queueItem.deviceId}) ${queueItem.log} due to more recent command in queue.`);
continue; // Skip commands if new ones exist for the same deviceId, but still process all messages in order
}
const success = await this.write(queueItem.payload);
if (!success && queueItem.shouldRetry) {
queueItem.retryCount = (queueItem.retryCount || 0) + 1;
dbgLogger('Will retry command, count failed so far', queueItem.retryCount);
logger.debug(`Will retry command, count failed so far ${queueItem.retryCount}`);
if (queueItem.retryCount <= MAX_RETRY_COUNT) {
this.writeQueue.push(queueItem); // Add back to top of queue to be processed next;
}
else {
errLogger(`Write queue: Exceeed max retry count (${MAX_RETRY_COUNT}) for ${deviceName} (${queueItem.deviceId}). Command ${queueItem.log} failed.`);
logger.error(`Write queue: Exceeed max retry count (${MAX_RETRY_COUNT}) for ${deviceName} (${queueItem.deviceId}). Command ${queueItem.log} failed.`);
break;
}
if (queueItem.retryCount > 1) {
@ -497,7 +477,7 @@ class PlejdService extends EventEmitter {
}
}
} catch (e) {
errLogger('Error in writeQueue loop, values probably not written to Plejd', e);
logger.error('Error in writeQueue loop, values probably not written to Plejd', e);
}
this.writeQueueRef = setTimeout(() => this.runWriteQueue(), this.writeQueueWaitTime);
@ -510,7 +490,7 @@ class PlejdService extends EventEmitter {
const uuid = (await properties.Get(GATT_SERVICE_ID, 'UUID')).value;
if (uuid !== PLEJD_SERVICE) {
errLogger('not a Plejd device.');
logger.error('not a Plejd device.');
return null;
}
@ -534,17 +514,17 @@ class PlejdService extends EventEmitter {
const chUuid = (await prop.Get(GATT_CHRC_ID, 'UUID')).value;
if (chUuid === DATA_UUID) {
dbgLogger('found DATA characteristic.');
logger.debug('found DATA characteristic.');
this.characteristics.data = ch;
} else if (chUuid === LAST_DATA_UUID) {
dbgLogger('found LAST_DATA characteristic.');
logger.debug('found LAST_DATA characteristic.');
this.characteristics.lastData = ch;
this.characteristics.lastDataProperties = prop;
} else if (chUuid === AUTH_UUID) {
dbgLogger('found AUTH characteristic.');
logger.debug('found AUTH characteristic.');
this.characteristics.auth = ch;
} else if (chUuid === PING_UUID) {
dbgLogger('found PING characteristic.');
logger.debug('found PING characteristic.');
this.characteristics.ping = ch;
}
}
@ -555,10 +535,10 @@ class PlejdService extends EventEmitter {
}
async onDeviceConnected(device) {
infLogger('onDeviceConnected()');
dbgLogger('Device: ', device);
logger.info('onDeviceConnected()');
logger.debug(`Device: ${device}`);
if (!device) {
errLogger('Device is null. Should we break/return when this happens?');
logger.error('Device is null. Should we break/return when this happens?');
}
const objects = await this.objectManager.GetManagedObjects();
@ -582,7 +562,7 @@ class PlejdService extends EventEmitter {
}
}
infLogger('trying ' + chPaths.length + ' characteristics');
logger.info('trying ' + chPaths.length + ' characteristics');
this.plejdService = await this._processPlejdService(path, chPaths);
if (this.plejdService) {
@ -592,13 +572,13 @@ class PlejdService extends EventEmitter {
}
if (!this.plejdService) {
infLogger('warning: wasn\'t able to connect to Plejd, will retry.');
logger.info('warning: wasn\'t able to connect to Plejd, will retry.');
this.emit('connectFailed');
return;
}
if (!this.characteristics.auth) {
errLogger('unable to enumerate characteristics.');
logger.error('unable to enumerate characteristics.');
this.emit('connectFailed');
return;
}
@ -632,19 +612,19 @@ class PlejdService extends EventEmitter {
const data2 = parseInt(decoded.toString('hex', 6, 8), 16) >> 8;
if (decoded.length < 5) {
dbgLogger('Too short raw event ignored: ', decoded.toString('hex'));
logger.debug(`Too short raw event ignored: ${decoded.toString('hex')}`);
// ignore the notification since too small
return;
}
const deviceName = (logVerbose || logDebug) ? this._getDeviceName(deviceId) : '';
vrbLogger('Raw event received: ', decoded.toString('hex'));
vrbLogger(`Device ${deviceId}, cmd ${cmd.toString('hex')}, state ${state}, dim/data2 ${data2}`);
const deviceName = this._getDeviceName(deviceId);
logger.verbose(`Raw event received: ${decoded.toString('hex')}`);
logger.verbose(`Device ${deviceId}, cmd ${cmd.toString('hex')}, state ${state}, dim/data2 ${data2}`);
if (cmd === BLE_CMD_DIM_CHANGE || cmd === BLE_CMD_DIM2_CHANGE) {
const dim = data2;
dbgLogger(`${deviceName} (${deviceId}) got state+dim update. S: ${state}, D: ${dim}`);
logger.debug(`${deviceName} (${deviceId}) got state+dim update. S: ${state}, D: ${dim}`);
this.emit('stateChanged', deviceId, {
state: state,
@ -655,9 +635,9 @@ class PlejdService extends EventEmitter {
state: state,
dim: dim
};
vrbLogger('All states: ', this.plejdDevices);
logger.verbose(`All states: ${JSON.stringify(this.plejdDevices)}`);
} else if (cmd === BLE_CMD_STATE_CHANGE) {
dbgLogger(`${deviceName} (${deviceId}) got state update. S: ${state}`);
logger.debug(`${deviceName} (${deviceId}) got state update. S: ${state}`);
this.emit('stateChanged', deviceId, {
state: state
});
@ -665,25 +645,25 @@ class PlejdService extends EventEmitter {
state: state,
dim: 0
};
vrbLogger('All states: ', this.plejdDevices);
logger.verbose(`All states: ${this.plejdDevices}`);
} else if (cmd === BLE_CMD_SCENE_TRIG) {
const sceneId = parseInt(decoded.toString('hex', 5, 6), 16);
const sceneName = this._getDeviceName(sceneId);
dbgLogger(`${sceneName} (${sceneId}) scene triggered (device id ${deviceId}). Name can be misleading if there is a device with the same numeric id.`);
logger.debug(`${sceneName} (${sceneId}) scene triggered (device id ${deviceId}). Name can be misleading if there is a device with the same numeric id.`);
this.emit('sceneTriggered', deviceId, sceneId);
}
else if (cmd === '001b') {
// vrbLogger('Command 001b seems to be some kind of often repeating ping/mesh data');
logger.silly('Command 001b seems to be some kind of often repeating ping/mesh data');
}
else {
vrbLogger(`Command ${cmd.toString('hex')} unknown. Device ${deviceName} (${deviceId})`);
logger.verbose(`Command ${cmd.toString('hex')} unknown. Device ${deviceName} (${deviceId})`);
}
}
wireEvents() {
infLogger('wireEvents()');
logger.info('wireEvents()');
const self = this;
this.on('pingFailed', this.onPingFailed.bind(self));

View file

@ -1,13 +1,18 @@
const api = require('./api');
const mqtt = require('./mqtt');
const fs = require('fs');
const Logger = require('./Logger');
const PlejdService = require('./ble.bluez');
const SceneManager = require('./scene.manager');
const logger = Logger.getLogger("plejd-main");
const version = "0.4.8";
async function main() {
console.log('starting Plejd add-on v. ' + version);
logger.info(`Starting Plejd add-on v. ${version}`);
const rawData = fs.readFileSync('/data/plejd.json');
const config = JSON.parse(rawData);
@ -28,7 +33,7 @@ async function main() {
const devices = plejdApi.getDevices();
client.on('connected', () => {
console.log('plejd-mqtt: connected to mqtt.');
logger.verbose('connected to mqtt.');
client.discover(devices);
});
@ -38,7 +43,7 @@ async function main() {
const sceneManager = new SceneManager(plejdApi.site, devices);
const plejd = new PlejdService(cryptoKey, devices, sceneManager, config.connectionTimeout, config.writeQueueWaitTime, true);
plejd.on('connectFailed', () => {
console.log('plejd-ble: were unable to connect, will retry connection in 10 seconds.');
logger.verbose('Were unable to connect, will retry connection in 10 seconds.');
setTimeout(() => {
plejd.init();
}, 10000);
@ -47,7 +52,7 @@ async function main() {
plejd.init();
plejd.on('authenticated', () => {
console.log('plejd: connected via bluetooth.');
logger.verbose('plejd: connected via bluetooth.');
});
// subscribe to changes from Plejd
@ -97,16 +102,6 @@ async function main() {
plejd.turnOff(deviceId, commandObj);
}
});
client.on('settingsChanged', (settings) => {
if (settings.module === 'mqtt') {
client.updateSettings(settings);
} else if (settings.module === 'ble') {
plejd.updateSettings(settings);
} else if (settings.module === 'api') {
plejdApi.updateSettings(settings);
}
});
});
});
});

View file

@ -1,22 +1,10 @@
const EventEmitter = require('events');
const mqtt = require('mqtt');
const _ = require('lodash');
const Logger = require('./Logger');
const startTopic = 'hass/status';
// #region logging
let debug = '';
const getLogger = () => {
const consoleLogger = msg => console.log('plejd-mqtt', msg);
if (debug === 'console') {
return consoleLogger;
}
return _.noop;
};
const logger = getLogger();
// #endregion
const logger = Logger.getLogger("plejd-mqtt");
// #region discovery
@ -30,7 +18,6 @@ const getConfigPath = plug => `${getPath(plug)}/config`;
const getStateTopic = plug => `${getPath(plug)}/state`;
const getCommandTopic = plug => `${getPath(plug)}/set`;
const getSceneEventTopic = () => `plejd/event/scene`;
const getSettingsTopic = () => `plejd/settings`;
const getDiscoveryPayload = device => ({
schema: 'json',
@ -77,6 +64,7 @@ class MqttClient extends EventEmitter {
}
init() {
logger.info("Initializing MQTT connection for Plejd addon");
const self = this;
this.client = mqtt.connect(this.mqttBroker, {
@ -85,11 +73,11 @@ class MqttClient extends EventEmitter {
});
this.client.on('connect', () => {
logger('connected to MQTT.');
logger.info('Connected to MQTT.');
this.client.subscribe(startTopic, (err) => {
if (err) {
logger('error: unable to subscribe to ' + startTopic);
logger.error(`Unable to subscribe to ${startTopic}`);
}
self.emit('connected');
@ -97,18 +85,14 @@ class MqttClient extends EventEmitter {
this.client.subscribe(getSubscribePath(), (err) => {
if (err) {
logger('error: unable to subscribe to control topics');
logger.error('Unable to subscribe to control topics');
}
});
this.client.subscribe(getSettingsTopic(), (err) => {
if (err) {
console.log('error: could not subscribe to settings topic');
}
});
});
this.client.on('close', () => {
logger.verbose('Warning: mqtt channel closed event, reconnecting...');
self.reconnect();
});
@ -119,27 +103,18 @@ class MqttClient extends EventEmitter {
: message.toString();
if (topic === startTopic) {
logger('home assistant has started. lets do discovery.');
logger.info('Home Assistant has started. lets do discovery.');
self.emit('connected');
}
else if (topic === getSettingsTopic()) {
self.emit('settingsChanged', command);
}
if (_.includes(topic, 'set')) {
else if (topic.includes('set')) {
logger.verbose(`Got mqtt command on ${topic} - ${message}`);
const device = self.devices.find(x => getCommandTopic(x) === topic);
self.emit('stateChanged', device, command);
}
});
}
updateSettings(settings) {
if (settings.debug) {
debug = 'console';
}
else {
debug = '';
logger.verbose(`Warning: Got unrecognized mqtt command on ${topic} - ${message}`);
}
});
}
reconnect() {
@ -150,13 +125,13 @@ class MqttClient extends EventEmitter {
this.devices = devices;
const self = this;
logger('sending discovery of ' + devices.length + ' device(s).');
logger.debug(`Sending discovery of ${devices.length} device(s).`);
devices.forEach((device) => {
logger(`sending discovery for ${device.name}`);
logger.debug(`Sending discovery for ${device.name}`);
let payload = device.type === 'switch' ? getSwitchPayload(device) : getDiscoveryPayload(device);
console.log(`plejd-mqtt: discovered ${device.type} (${device.typeName}) named ${device.name} with PID ${device.id}.`);
logger.info(`Discovered ${device.type} (${device.typeName}) named ${device.name} with PID ${device.id}.`);
self.deviceMap[device.id] = payload.unique_id;
@ -171,11 +146,11 @@ class MqttClient extends EventEmitter {
const device = this.devices.find(x => x.id === deviceId);
if (!device) {
logger('error: ' + deviceId + ' is not handled by us.');
logger.warn(`Unknown device id ${deviceId} - not handled by us.`);
return;
}
logger('updating state for ' + device.name + ': ' + data.state);
logger.verbose(`Updating state for ${device.name}: ${data.state}`);
let payload = null;
if (device.type === 'switch') {
@ -204,6 +179,7 @@ class MqttClient extends EventEmitter {
}
sceneTriggered(scene) {
logger.verbose(`Scene triggered: ${scene}`);
this.client.publish(
getSceneEventTopic(),
JSON.stringify({ scene: scene })

206
plejd/package-lock.json generated
View file

@ -13,6 +13,16 @@
"usb": "^1.6.0"
}
},
"@dabh/diagnostics": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz",
"integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==",
"requires": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"@nornagon/put": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@nornagon/put/-/put-0.0.8.tgz",
@ -52,6 +62,11 @@
"readable-stream": "^2.0.6"
}
},
"async": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
"integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
},
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
@ -147,6 +162,51 @@
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"color": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
"requires": {
"color-convert": "^1.9.1",
"color-string": "^1.5.2"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz",
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"colorspace": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
"integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
"requires": {
"color": "3.0.x",
"text-hex": "1.0.x"
}
},
"commist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz",
@ -199,9 +259,9 @@
}
},
"dbus-next": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.8.1.tgz",
"integrity": "sha512-/sgwpcnCkLQuMOTF9I95x6qvJZCbTK2RAbHwh7C60VMroSU7rphydj3ujpqiSy5yq04aKc3meZIHpCOrZ2791g==",
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.9.1.tgz",
"integrity": "sha512-HTFKjrcNcjdjNlZR+/5LlUHBw13d+ZHQA51tfn8xUzm3WYi7uWQ99M8M4bttATCth3l4G/fxu3XC97JZ5K0oSQ==",
"requires": {
"@nornagon/put": "0.0.8",
"abstract-socket": "^2.0.0",
@ -246,9 +306,9 @@
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"duplexer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="
},
"duplexify": {
"version": "3.7.1",
@ -261,6 +321,11 @@
"stream-shift": "^1.0.0"
}
},
"enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -383,12 +448,27 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"fecha": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz",
"integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg=="
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"optional": true
},
"fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"follow-redirects": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
@ -548,6 +628,11 @@
"is-windows": "^1.0.1"
}
},
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -582,6 +667,11 @@
"is-unc-path": "^1.0.0"
}
},
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"is-unc-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz",
@ -615,15 +705,27 @@
"resolved": "https://registry.npmjs.org/jspack/-/jspack-0.0.4.tgz",
"integrity": "sha1-Mt01x/3LPjRWwY+7fvntC8YjgXc="
},
"kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"leven": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA="
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
"logform": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz",
"integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==",
"requires": {
"colors": "^1.2.1",
"fast-safe-stringify": "^2.0.4",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"triple-beam": "^1.3.0"
}
},
"long": {
"version": "4.0.0",
@ -855,6 +957,14 @@
"wrappy": "1"
}
},
"one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"requires": {
"fn.name": "1.x.x"
}
},
"ordered-read-streams": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz",
@ -1063,6 +1173,14 @@
"simple-concat": "^1.0.0"
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
"requires": {
"is-arrayish": "^0.3.1"
}
},
"sleep": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/sleep/-/sleep-6.1.0.tgz",
@ -1099,6 +1217,11 @@
}
}
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
},
"stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
@ -1195,6 +1318,11 @@
}
}
},
"text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@ -1227,6 +1355,11 @@
"is-negated-glob": "^1.0.0"
}
},
"triple-beam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -1316,6 +1449,59 @@
"string-width": "^1.0.2 || 2"
}
},
"winston": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz",
"integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==",
"requires": {
"@dabh/diagnostics": "^2.0.2",
"async": "^3.1.0",
"is-stream": "^2.0.0",
"logform": "^2.2.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.4.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"winston-transport": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz",
"integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==",
"requires": {
"readable-stream": "^2.3.7",
"triple-beam": "^1.2.0"
},
"dependencies": {
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
}
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

View file

@ -1,13 +1,13 @@
{
"dependencies": {
"@abandonware/bluetooth-hci-socket": "0.5.3-3",
"axios": "^0.21.1",
"buffer-xor": "^2.0.2",
"dbus-next": "^0.8.1",
"axios": "~0.21.1",
"buffer-xor": "~2.0.2",
"dbus-next": "~0.9.1",
"fs": "0.0.1-security",
"jspack": "0.0.4",
"lodash": "^4.17.19",
"mqtt": "^3.0.0",
"sleep": "^6.1.0"
"jspack": "~0.0.4",
"mqtt": "~3.0.0",
"sleep": "~6.1.0",
"winston": "~3.3.3"
}
}

View file

@ -1,5 +1,4 @@
const EventEmitter = require('events');
const _ = require('lodash');
class SceneManager extends EventEmitter {
constructor(site, devices) {