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:
parent
ba27dd6d18
commit
4176cfb714
9 changed files with 434 additions and 214 deletions
|
|
@ -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
99
plejd/Logger.js
Normal 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;
|
||||
60
plejd/api.js
60
plejd/api.js
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
206
plejd/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
const EventEmitter = require('events');
|
||||
const _ = require('lodash');
|
||||
|
||||
class SceneManager extends EventEmitter {
|
||||
constructor(site, devices) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue