From a96f665307f92f14136d1eda10e520f6782219b3 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 2 Feb 2020 19:00:05 +0100 Subject: [PATCH 001/236] rflink service --- .../services/rflink/api/rflink.controller.js | 72 ++++ .../rflink/api/rflink.parse.ObjToRF.js | 25 ++ .../rflink/api/rflink.parse.RFtoObject.js | 45 +++ server/services/rflink/index.js | 58 +++ .../rflink/lib/commands/rflink.connect.js | 26 ++ .../rflink/lib/commands/rflink.disconnect.js | 20 + .../rflink/lib/commands/rflink.getDevices.js | 16 + .../rflink/lib/commands/rflink.listen.js | 18 + .../rflink/lib/commands/rflink.setValue.js | 26 ++ .../rflink/lib/events/rflink.addDevice.js | 34 ++ .../rflink/lib/events/rflink.connected.js | 20 + .../rflink/lib/events/rflink.error.js | 20 + .../rflink/lib/events/rflink.message.js | 372 ++++++++++++++++++ .../rflink/lib/events/rflink.newValue.js | 24 ++ server/services/rflink/lib/index.js | 62 +++ server/services/rflink/package.json | 21 + 16 files changed, 859 insertions(+) create mode 100644 server/services/rflink/api/rflink.controller.js create mode 100644 server/services/rflink/api/rflink.parse.ObjToRF.js create mode 100644 server/services/rflink/api/rflink.parse.RFtoObject.js create mode 100644 server/services/rflink/index.js create mode 100644 server/services/rflink/lib/commands/rflink.connect.js create mode 100644 server/services/rflink/lib/commands/rflink.disconnect.js create mode 100644 server/services/rflink/lib/commands/rflink.getDevices.js create mode 100644 server/services/rflink/lib/commands/rflink.listen.js create mode 100644 server/services/rflink/lib/commands/rflink.setValue.js create mode 100644 server/services/rflink/lib/events/rflink.addDevice.js create mode 100644 server/services/rflink/lib/events/rflink.connected.js create mode 100644 server/services/rflink/lib/events/rflink.error.js create mode 100644 server/services/rflink/lib/events/rflink.message.js create mode 100644 server/services/rflink/lib/events/rflink.newValue.js create mode 100644 server/services/rflink/lib/index.js create mode 100644 server/services/rflink/package.json diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js new file mode 100644 index 0000000000..e59aee8b58 --- /dev/null +++ b/server/services/rflink/api/rflink.controller.js @@ -0,0 +1,72 @@ +const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); +const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); +const logger = require('../../../utils/logger'); + +module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { + /** + * @api {get} /api/v1/service/rflink/devices get rflink devices + * @apiName getDevices + * @apiGroup RFlink + */ + async function getDevices(req, res) { + logger.log('getting devices ...'); + res.json(RFlinkManager.getDevices()); + } + + /** + * @api {get} /api/v1/service/rflink/connect connect to the gateway + * @apiName connect + * @apiGroup RFlink + */ + async function connect(req, res) { + const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); + if (!rflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } + RFlinkManager.connect(rflinkPath); + res.json({succes: true, }); + } + /** + * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway + * @apiName disconnect + * @apiGroup RFlink + */ + async function disconnect(req, res) { + RFlinkManager.disconnect(); + res.json({ + success: true, + }); + } + /** + * @api {get} /api/v1/service/rflink/status get the gateway's status + * @apiName getStatus + * @apiGroup RFlink + */ + async function getStatus(req, res) { + logger.log('getting status'); + res.json({ + connected: RFlinkManager.connected, + scanInProgress: RFlinkManager.scanInProgress, + }); + } + + return { + 'get /api/v1/service/rflink/devices' : { + authenticated: true, + controller: asyncMiddleware(getDevices) + }, + 'post /api/v1/service/rflink/connect' : { + authenticated: true, + controller: asyncMiddleware(connect) + }, + 'post /api/v1/service/rflink/disconnect' : { + authenticated: true, + controller: asyncMiddleware(disconnect) + }, + 'get /api/v1/sevice/rflink/status' : { + authenticated: true, + controller: asyncMiddleware(getStatus) + } + + }; +}; \ No newline at end of file diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js new file mode 100644 index 0000000000..f01c8f32b2 --- /dev/null +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -0,0 +1,25 @@ + +// eslint-disable-next-line jsdoc/check-alignment +/** +* @description convert a rflink device object to a string that can be sent to rflink +* @param {Object} device - Secure node. +* @example +* rflink.ObjToRF(device); +*/ +function ObjToRF(device) { + const id = device.external_id.slit(':')[1]; + + let Rfcode = `10;${device.model};${id};`; + + for (let i = 0;i { + this.message(data); + + + + }); +} + +module.exports = { + listen, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js new file mode 100644 index 0000000000..08ece16789 --- /dev/null +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -0,0 +1,26 @@ +const ObjToRF = require('../../api/rflink.parse.ObjToRF'); +const logger = require('../../../../utils/logger'); +const { EVENTS } = require('../../../../utils/constants'); +/** + * @description send a message to change a device's value + * @param {Object} device - The device to control. + * @param {Object} deviceFeature - The feature to control. + * @param {Object} state - The new state. + * @example + * rflink.SetValue(2a11d,{name : action , value : ON}); + */ +function SetValue(device, deviceFeature, state) { + this.newValue(device, deviceFeature, state); + + + + const msg = ObjToRF.ObjToRF(device); + this.usb.write(msg); + + +} + + +module.exports = { + SetValue, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.addDevice.js b/server/services/rflink/lib/events/rflink.addDevice.js new file mode 100644 index 0000000000..504d24b77f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.addDevice.js @@ -0,0 +1,34 @@ +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-restricted-syntax */ +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + + +/** + * @description Add device. + * @param {Object} device - Device to add. + * @example + * Rflink.addDevice(device); + */ +function addDevice(device) { + const id = device.external_id.split(':')[1]; + this.gladys.event.emit(EVENTS.DEVICE.NEW, { + device + }); + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, + payload : device, + }); + this.device[id] = device; + + + + + + + + + + +} + +module.exports = {addDevice}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.connected.js b/server/services/rflink/lib/events/rflink.connected.js new file mode 100644 index 0000000000..9770f9359f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.connected.js @@ -0,0 +1,20 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When the gateway is connected + * @example + * rflink.on('connected', this.connected); + */ +function connected() { + logger.debug(`Rflink : Gateway is connected`); + this.connected = true; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, + payload: {}, + }); +} + +module.exports = { + connected, +}; diff --git a/server/services/rflink/lib/events/rflink.error.js b/server/services/rflink/lib/events/rflink.error.js new file mode 100644 index 0000000000..52f363e50f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.error.js @@ -0,0 +1,20 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When an error occur + * @example + * rflink.on('failed', this.driverFailed); + */ +function failed() { + logger.debug(`RFlink: Failed to start`); + this.connected = false; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.ERROR, + payload: {}, + }); +} + +module.exports = { + failed, +}; diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js new file mode 100644 index 0000000000..1acf504f7f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.message.js @@ -0,0 +1,372 @@ +const logger = require('../../../../utils/logger'); +const RFtoObj = require('../../api/rflink.parse.RFtoObject'); +const { + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES, + DEVICE_FEATURE_UNITS, + } = require('../../../../utils/constants'); + + + +/** + * @description when a message is received by the rflink gateway + * @param {string} msgRF - The message. + * @example + * rflink.message(msg); + */ +function message(msgRF) { + + const msg = RFtoObj(msgRF); + let newDevice; + + if (typeof msg.id === 'string'){ + if (msg.id.includes('=') === false) { + + const doesntExistYet = this.device[msg.id] === undefined; + + if (doesntExistYet === true) { + + + newDevice = { + service_id : this.serviceId, + name : `${msg.protocol} `, + selector : `rflink:${msg.id}`, + external_id: `rflink:${msg.id}`, + model : `${msg.protocol}`, + features : [] + }; + + + if (msg.temp !== undefined) { + newDevice.name += 'temperature sensor'; + newDevice.features.push({ + name : 'temperature', + selector : `rflink:${msg.id}:temperature`, + external_id : `rflink:${msg.id}:temperature`, + rfcode : 'TEMP', + category : DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.CELSIUS, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.hum !== undefined) { + newDevice.features.push({ + name : 'humidity', + selector : `rflink:${msg.id}:humidity`, + external_id : `rflink:${msg.id}:humidity`, + rfcode : 'HUM', + category : DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.baro !== undefined) { + newDevice.name += 'pressure sensor'; + newDevice.features.push({ + name : 'pressure', + selector : `rflink:${msg.id}:pressure`, + external_id : `rflink:${msg.id}:pressure`, + rfcode : 'BARO', + category : DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PASCAL, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100000000, + + + }); + } + if (msg.uv !== undefined) { + newDevice.name += 'uv sensor'; + newDevice.features.push({ + name : 'uv intensity', + selector : `rflink:${msg.id}:uv`, + external_id : `rflink:${msg.id}:uv`, + rfcode : 'UV', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.lux !== undefined) { + newDevice.name += 'light sensor'; + newDevice.features.push({ + name : 'light intensity', + selector : `rflink:${msg.id}:light-intensity`, + external_id : `rflink:${msg.id}:light-intensity`, + rfcode : 'LUX', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.LUX, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.bat !== undefined) { + newDevice.features.push({ + name : 'battery', + selector : `rflink:${msg.id}:battery`, + external_id : `rflink:${msg.id}:battery`, + rfcode : 'BAT', + category : DEVICE_FEATURE_CATEGORIES.BATTERY, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.rain !== undefined || msg.rainrate !== undefined) { + newDevice.name += 'rain sensor'; + newDevice.features.push({ + name : 'rain', + selector : `rflink:${msg.id}:rain`, + external_id : `rflink:${msg.id}:rain`, + rfcode : 'RAIN', + category : DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { + newDevice.name += 'wind speed sensor'; + newDevice.features.push({ + name : 'wind speed', + selector : `rflink:${msg.id}:wind-speed`, + external_id : `rflink:${msg.id}:wind-speed`, + rfcode : 'WINSP', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 500, + + + }); + } + if (msg.windir !== undefined) { + newDevice.name += 'wind direction sensor'; + newDevice.features.push({ + name : 'wind direction', + selector : `rflink:${msg.id}:wind-dir`, + external_id : `rflink:${msg.id}:wind-dir`, + rfcode : 'WINDIR', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.co2 !== undefined) { + newDevice.name += 'co2 sensor'; + newDevice.features.push({ + name : 'co2', + selector : `rflink:${msg.id}:co2`, + external_id : `rflink:${msg.id}:co2`, + rfcode : 'CO2', + category : DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + newDevice.name += 'switch'; + newDevice.features.push({ + name : 'switch', + selector : `rflink:${msg.id}:switch`, + external_id : `rflink:${msg.id}:switch`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.SWITCH, + type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only : false, + keep_history: true, + has_feedback: false, + min: 'OFF', + max: 'ON', + + + }); + } + + + if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { + newDevice.features.push({ + name : 'color', + selector : `rflink:${msg.id}:color`, + external_id : `rflink:${msg.id}:color`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 255, + + + }); + newDevice.features.push({ + name : 'brightness', + selector : `rflink:${msg.id}:brightness`, + external_id : `rflink:${msg.id}:brightness`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + newDevice.features.push({ + name : 'milight-mode', + selector : `rflink:${msg.id}:milight-mode`, + external_id : `rflink:${msg.id}:milight-mode`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.MODE, + read_only : false, + keep_history: true, + has_feedback: false, + min: 1, + max: 8, + + + }); + + + } + + + this.addDevice(newDevice); + + } else if (doesntExistYet === false) { + + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.hum !== undefined) { + this.newValue(msg, 'humidity', msg.hum); + } + if (msg.uv !== undefined) { + this.newValue(msg, 'uv', msg.uv); + } + if (msg.lux !== undefined) { + this.newValue(msg, 'light-intensity', msg.lux); + } + if (msg.bat !== undefined) { + this.newValue(msg, 'battery', msg.bat); + } + if (msg.rain !== undefined) { + this.newValue(msg, 'rain', msg.rain); + } + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.winsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.winsp); + } + if (msg.awinsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.awinsp); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.windir !== undefined) { + this.newValue(msg, 'wind-dir', msg.windir); + } + if (msg.co2 !== undefined) { + this.newValue(msg, 'co2', msg.co2); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + this.newValue(msg, 'switch', msg.cmd); + } + if (msg.rgbw !== undefined) { + this.newValue(msg, 'color', msg.rgbw); + this.newValue(msg, 'brightness', msg.rgbw); + } + if (msg.cmd.includes('MODE') === true ) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + if (msg.cmd.includes('DISCO') === true) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + + + + + + + } + // const features = + + }else { + logger.log(`${msg.id} n'est pas une id valide`); + } + + } + + + +} + +module.exports = { + message, +}; + diff --git a/server/services/rflink/lib/events/rflink.newValue.js b/server/services/rflink/lib/events/rflink.newValue.js new file mode 100644 index 0000000000..cff2f488c0 --- /dev/null +++ b/server/services/rflink/lib/events/rflink.newValue.js @@ -0,0 +1,24 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS } = require('../../../../utils/constants'); +// eslint-disable-next-line jsdoc/require-param +/** + * @description When a new value is received. + * @param {Object} device - Device to update. + * @param {string} deviceFeature - Feature to update. + * @example + * newValue(Object, 'temperature', 30) + */ +function newValue(device, deviceFeature, state) { + + logger.debug(`RFlink : value ${deviceFeature} of device ${device.id} changed to ${state}`); + + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, + state, + }); + +} + +module.exports = { + newValue, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js new file mode 100644 index 0000000000..b22177ca45 --- /dev/null +++ b/server/services/rflink/lib/index.js @@ -0,0 +1,62 @@ +const logger = require('../../../utils/logger'); + +// Events + +const { newValue } = require('./events/rflink.newValue'); +const { addDevice } = require('./events/rflink.addDevice'); +const { message } = require('./events/rflink.message.js'); +const { error } = require('./events/rflink.error'); + + +// Commands + +const { setValue } = require('./commands/rflink.setValue'); +const { connect } = require('./commands/rflink.connect'); +const { disconnect } = require('./commands/rflink.disconnect'); +const { listen } = require('./commands/rflink.listen'); +const { getDevices } = require('./commands/rflink.getDevices'); + + + + + +const RFlinkManager = function RFlinkManager(usb, gladys, serviceId) { + this.usb = usb; + this.gladys = gladys; + this.scanInProgress = false; + this.serviceId = serviceId; + this.connected = false; + this.device = {}; + + +}; + +// Events + +RFlinkManager.prototype.message = message; +RFlinkManager.prototype.newValue = newValue; +RFlinkManager.prototype.addDevice = addDevice; +RFlinkManager.prototype.error = error; + + +// Commands + +RFlinkManager.prototype.setValue = setValue; +RFlinkManager.prototype.connect = connect; +RFlinkManager.prototype.disconnect = disconnect; +RFlinkManager.prototype.listen = listen; +RFlinkManager.prototype.getDevices = getDevices; + + + + + + + + + + + + + +module.exports = RFlinkManager; \ No newline at end of file diff --git a/server/services/rflink/package.json b/server/services/rflink/package.json new file mode 100644 index 0000000000..eaadda0035 --- /dev/null +++ b/server/services/rflink/package.json @@ -0,0 +1,21 @@ +{ + "author": { + "name": "mathis tondenier" + }, + "name": "gladys-rflink", + "main": "index.js", + "os": [ + "darwin", + "linux", + "win32" + ], + "cpu": [ + "x64", + "arm", + "arm64" + ], + "dependencies": { + "serialport": "8.0.6", + "@serialport/parser-readline": "8.0.6" + } +} From 54386e40ae8f6bb52c39285983b575c8f26e4f86 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 6 Feb 2020 20:06:45 +0100 Subject: [PATCH 002/236] Add UI --- .../integration/all/rflink/RflinkPage.jsx | 49 +++++++ .../all/rflink/device-page/Device.jsx | 60 +++++++++ .../all/rflink/device-page/DeviceForm.jsx | 84 ++++++++++++ .../all/rflink/device-page/DevicePage.jsx | 69 ++++++++++ .../all/rflink/device-page/FoundDevices.jsx | 69 ++++++++++ .../all/rflink/device-page/actions.js | 125 ++++++++++++++++++ .../all/rflink/device-page/index.js | 29 ++++ .../all/rflink/device-page/style.css | 3 + .../all/rflink/settings-page/SettingsTab.jsx | 78 +++++++++++ .../all/rflink/settings-page/actions.js | 107 +++++++++++++++ .../all/rflink/settings-page/index.js | 50 +++++++ 11 files changed, 723 insertions(+) create mode 100644 front/src/routes/integration/all/rflink/RflinkPage.jsx create mode 100644 front/src/routes/integration/all/rflink/device-page/Device.jsx create mode 100644 front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx create mode 100644 front/src/routes/integration/all/rflink/device-page/DevicePage.jsx create mode 100644 front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx create mode 100644 front/src/routes/integration/all/rflink/device-page/actions.js create mode 100644 front/src/routes/integration/all/rflink/device-page/index.js create mode 100644 front/src/routes/integration/all/rflink/device-page/style.css create mode 100644 front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx create mode 100644 front/src/routes/integration/all/rflink/settings-page/actions.js create mode 100644 front/src/routes/integration/all/rflink/settings-page/index.js diff --git a/front/src/routes/integration/all/rflink/RflinkPage.jsx b/front/src/routes/integration/all/rflink/RflinkPage.jsx new file mode 100644 index 0000000000..e903338aa7 --- /dev/null +++ b/front/src/routes/integration/all/rflink/RflinkPage.jsx @@ -0,0 +1,49 @@ +import { Text } from 'preact-i18n'; +import { Link } from 'preact-router/match'; + +const RflinkPage = ({ children, ...props }) => ( +
+
+
+
+
+
+

+ +

+
+
+ + + + + + + + + + + + + +
+
+
+ +
{children}
+
+
+
+
+
+); + +export default RflinkPage; diff --git a/front/src/routes/integration/all/rflink/device-page/Device.jsx b/front/src/routes/integration/all/rflink/device-page/Device.jsx new file mode 100644 index 0000000000..2858204096 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/Device.jsx @@ -0,0 +1,60 @@ +import { Text } from 'preact-i18n'; +import { Component } from 'preact'; +import cx from 'classnames'; +import { RequestStatus } from '../../../../../utils/consts'; +import DeviceForm from './DeviceForm'; + +class RflinkDeviceBox extends Component { + saveDevice = async () => { + this.setState({ loading: true }); + try { + await this.props.saveDevice(this.props.device, this.props.deviceIndex); + } catch (e) { + this.setState({ error: RequestStatus.Error }); + } + this.setState({ loading: false }); + }; + + deleteDevice = async () => { + this.setState({ loading: true }); + try { + await this.props.deleteDevice(this.props.device, this.props.deviceIndex); + } catch (e) { + this.setState({ error: RequestStatus.Error }); + } + this.setState({ loading: false }); + }; + + render(props, { loading }) { + return ( +
+
+
{props.device.name || }
+
+
+
+
+ + +
+ + +
+
+
+
+
+
+ ); + } +} + +export default RflinkDeviceBox; diff --git a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx new file mode 100644 index 0000000000..af01ba5b9b --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx @@ -0,0 +1,84 @@ +import { Text, Localizer } from 'preact-i18n'; +import { Component } from 'preact'; +import { DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; +import get from 'get-value'; + +class RflinkDeviceForm extends Component { + updateName = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'name', e.target.value); + }; + + updateRoom = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); + }; + + updateExternalId = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'external_id', e.target.value); + }; + + render({ ...props }) { + return ( +
+
+ + + } + /> + +
+ +
+ + +
+ +
+ +
+ {props.device && + props.device.features && + props.device.features.map(feature => ( + + +
+ +
+
+ ))} + {(!props.device.features || props.device.features.length === 0) && ( + + )} +
+
+
+ ); + } +} + +export default RflinkDeviceForm; diff --git a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx new file mode 100644 index 0000000000..7aca5746c5 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx @@ -0,0 +1,69 @@ +import { Text, Localizer } from 'preact-i18n'; +import cx from 'classnames'; + +import style from './style.css'; +import { RequestStatus } from '../../../../../utils/consts'; +import Device from './Device'; + +const DeviceTab = ({ children, ...props }) => ( +
+
+

+ +

+
+ +
+ + + + + } + onInput={props.debouncedSearch} + /> + +
+
+
+
+
+
+
+ {props.getRflinkDevicesStatus === RequestStatus.Getting &&
} +
+ {props.rflink && + props.rflink.map((device, index) => ( + + ))} + {props.rflink && props.rflink.length === 0 && ( + + )} +
+
+
+
+
+); + +export default DeviceTab; diff --git a/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx b/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx new file mode 100644 index 0000000000..d6b5931f0b --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx @@ -0,0 +1,69 @@ +import { Text } from 'preact-i18n'; +import cx from 'classnames'; + +import style from './style.css'; +import { RequestStatus } from '../../../../../utils/consts'; + +const createDevice = (props, device) => () => { + props.createDevice(device); +}; + +const FoundDevices = ({ children, ...props }) => ( +
+
+

+ +

+
+
+
+
+
+ {props.getRflinkDevicesStatus === RequestStatus.Getting &&
} +
+ {props.rflinkNewDevices && props.rflinkNewDevices.length === 0 && ( +
+
+ +
+
+ )} + {props.rflinkNewDevices && + props.rflinkNewDevices.map((device, index) => ( +
+
+
+

{device.name}

+
+
+ {!device.not_handled && ( + + )} + {device.not_handled && ( +
+ +
+ )} +
+
+
+ ))} + {props.rflinkDevices && props.rflinkDevices.length === 0 && ( + + )} +
+
+
+
+
+); + +export default FoundDevices; diff --git a/front/src/routes/integration/all/rflink/device-page/actions.js b/front/src/routes/integration/all/rflink/device-page/actions.js new file mode 100644 index 0000000000..bb3c5a74ad --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/actions.js @@ -0,0 +1,125 @@ +import { RequestStatus } from '../../../../../utils/consts'; +import update from 'immutability-helper'; +import createActionsHouse from '../../../../../actions/house'; +import createActionsIntegration from '../../../../../actions/integration'; +import debounce from 'debounce'; + +function createActions(store) { + const houseActions = createActionsHouse(store); + const integrationActions = createActionsIntegration(store); + const actions = { + async getRflinkDevices(state) { + store.setState({ + getrflinkDevicesStatus: RequestStatus.Getting + }); + try { + const options = { + service: 'rflink', + order_dir: state.getRflinkDeviceOrderDir || 'asc', + take: 10000, + skip: 0 + }; + if (state.rflinkDeviceSearch && state.rflinkDeviceSearch.length) { + options.search = state.rflinkDeviceSearch; + } + const rflinkDevices = await state.httpClient.get('/api/v1/service/rflink/device', options); + const rflinkDevicesMap = new Map(); + rflinkDevices.forEach(device => rflinkDevicesMap.set(device.external_id, device)); + store.setState({ + rflinkDevices, + rflinkDevicesMap, + getRflinkDevicesStatus: RequestStatus.Success + }); + actions.getRflinkNewDevices(store.getState()); + } catch (e) { + store.setState({ + getRflinkDevicesStatus: RequestStatus.Error + }); + } + }, + async getRflinkNewDevices(state) { + store.setState({ + getRflinkNewDevicesStatus: RequestStatus.Getting + }); + try { + const rflinkNewDevices = await state.httpClient.get('/api/v1/service/philips-hue/devices'); + const rflinkNewDevicesFiltered = rflinkNewDevices.filter(device => { + if (!state.rflinkDevicesMap) { + return true; + } + return !state.rflinkDevicesMap.has(device.external_id); + }); + store.setState({ + rflinkNewDevices: rflinkNewDevicesFiltered, + getRflinkNewDevicesStatus: RequestStatus.Success + }); + } catch (e) { + store.setState({ + getRflinkNewDevicesStatus: RequestStatus.Error + }); + } + }, + async saveDevice(state, device, index) { + const savedDevice = await state.httpClient.post('/api/v1/device', device); + const newState = update(state, { + rflinkDevices: { + $splice: [[index, 1, savedDevice]] + } + }); + store.setState(newState); + }, + async createDevice(state, device) { + store.setState({ + getRflinkCreateDeviceStatus: RequestStatus.Getting + }); + try { + await state.httpClient.post('/api/v1/device', device); + store.setState({ + getRflinkCreateDeviceStatus: RequestStatus.Success + }); + actions.getRflinkDevices(store.getState()); + } catch (e) { + store.setState({ + getRflinkCreateDeviceStatus: RequestStatus.Error + }); + } + }, + updateDeviceProperty(state, index, property, value) { + const newState = update(state, { + rflinkDevices: { + [index]: { + [property]: { + $set: value + } + } + } + }); + store.setState(newState); + }, + async deleteDevice(state, device, index) { + await state.httpClient.delete('/api/v1/device/' + device.selector); + const newState = update(state, { + rflinkDevices: { + $splice: [[index, 1]] + } + }); + store.setState(newState); + }, + async search(state, e) { + store.setState({ + rflinkDeviceSearch: e.target.value + }); + await actions.getRflinkDevices(store.getState()); + }, + async changeOrderDir(state, e) { + store.setState({ + getRflinkDeviceOrderDir: e.target.value + }); + await actions.getRflinkDevices(store.getState()); + } + }; + actions.debouncedSearch = debounce(actions.search, 200); + return Object.assign({}, houseActions, integrationActions, actions); +} + +export default createActions; diff --git a/front/src/routes/integration/all/rflink/device-page/index.js b/front/src/routes/integration/all/rflink/device-page/index.js new file mode 100644 index 0000000000..156b111667 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/index.js @@ -0,0 +1,29 @@ +import { Component } from 'preact'; +import { connect } from 'unistore/preact'; +import actions from './actions'; +import DevicePage from './DevicePage'; +import FoundDevices from './FoundDevices'; +import RflinkPage from '../RflinkPage.jsx'; + +@connect( + 'session,user,rflinksDevices,houses,getRflinkDevicesStatus,rflinkNewDevices,getRflinkCreateDeviceStatus,getRflinkNewDevicesStatus', + actions +) +class RflinkDevicePage extends Component { + componentWillMount() { + this.props.getRflinkDevices(); + this.props.getHouses(); + this.props.getIntegrationByName('rflink'); + } + + render(props, {}) { + return ( + + {props.rflinkDevices && props.rflinkDevices.length ? :
} + + + ); + } +} + +export default RflinkDevicePage; diff --git a/front/src/routes/integration/all/rflink/device-page/style.css b/front/src/routes/integration/all/rflink/device-page/style.css new file mode 100644 index 0000000000..67713e8fb7 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/style.css @@ -0,0 +1,3 @@ +.emptyDiv { + min-height: 200px +} diff --git a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx new file mode 100644 index 0000000000..8133a4a02a --- /dev/null +++ b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx @@ -0,0 +1,78 @@ +import { Text } from 'preact-i18n'; +import get from 'get-value'; +import cx from 'classnames'; + +const SettingsTab = ({ children, ...props }) => ( +
+
+

+ +

+
+ +
+
+
+
+
+
+ {get(props, 'rflinkStatus.connected') && ( +
+ +
+ )} + {!get(props, 'rflinkStatus.connected') && ( +
+ +
+ )} + {props.rflinkConnectionInProgress && ( +
+ +
+ )} + {props.rflinkFailed && ( +
+ +
+ )} +

+ +

+
+ + +
+
+ + +
+
+
+
+
+); + +export default SettingsTab; diff --git a/front/src/routes/integration/all/rflink/settings-page/actions.js b/front/src/routes/integration/all/rflink/settings-page/actions.js new file mode 100644 index 0000000000..349bab0cd5 --- /dev/null +++ b/front/src/routes/integration/all/rflink/settings-page/actions.js @@ -0,0 +1,107 @@ +import { RequestStatus } from '../../../../../utils/consts'; + +const actions = store => { + const actions = { + async getUsbPorts(state) { + store.setState({ + getRflinkUsbPortStatus: RequestStatus.Getting + }); + try { + const usbPorts = await state.httpClient.get('/api/v1/service/usb/port'); + store.setState({ + usbPorts, + getRflinkUsbPortStatus: RequestStatus.Success + }); + } catch (e) { + store.setState({ + getRflinkUsbPortStatus: RequestStatus.Error + }); + } + }, + async getCurrentRflinkPath(state) { + store.setState({ + getCurrentRflinkPathStatus: RequestStatus.Getting + }); + try { + const RflinkPath = await state.httpClient.get('/api/v1/service/rflink/variable/RFLINK_PATH'); + store.setState({ + RflinkPath: RflinkPath.value, + getCurrentRflinkPathStatus: RequestStatus.Success + }); + } catch (e) { + store.setState({ + getCurrentRflinkPathStatus: RequestStatus.Error + }); + } + }, + updateRflinkPath(state, e) { + store.setState({ + RflinkPath: e.target.value + }); + }, + async saveDriverPathAndConnect(state) { + store.setState({ + connectRflinkStatus: RequestStatus.Getting, + rflinkFailed: false + }); + try { + await state.httpClient.post('/api/v1/service/rflink/variable/RFLINK_PATH', { + value: state.RflinkPath + }); + await state.httpClient.post('/api/v1/service/rflink/connect'); + store.setState({ + connectRflinkStatus: RequestStatus.Success, + rflinkConnectionInProgress: true + }); + } catch (e) { + store.setState({ + connectRflinkStatus: RequestStatus.Error + }); + } + }, + async disconnect(state) { + store.setState({ + rflinkDisconnectStatus: RequestStatus.Getting + }); + try { + await state.httpClient.post('/api/v1/service/rflink/disconnect'); + await actions.getStatus(store.getState()); + store.setState({ + rflinkDisconnectStatus: RequestStatus.Success + }); + } catch (e) { + store.setState({ + rflinkDisconnectStatus: RequestStatus.Error + }); + } + }, + async getStatus(state) { + store.setState({ + rflinkGetStatusStatus: RequestStatus.Getting + }); + try { + const rflinkStatus = await state.httpClient.get('/api/v1/service/rflink/status'); + store.setState({ + rflinkStatus, + rflinkConnectionInProgress: false, + rflinkGetStatusStatus: RequestStatus.Success + }); + } catch (e) { + store.setState({ + rflinkGetStatusStatus: RequestStatus.Error, + rflinkConnectionInProgress: false + }); + } + }, + driverFailed(state) { + store.setState({ + rflinkFailed: true, + rflinkConnectionInProgress: false + }); + } + }; + + return actions; +}; + +export default actions; diff --git a/front/src/routes/integration/all/rflink/settings-page/index.js b/front/src/routes/integration/all/rflink/settings-page/index.js new file mode 100644 index 0000000000..596363ba45 --- /dev/null +++ b/front/src/routes/integration/all/rflink/settings-page/index.js @@ -0,0 +1,50 @@ +import { Component } from 'preact'; +import { connect } from 'unistore/preact'; +import actions from './actions'; +import RflinkPage from '../RflinkPage'; +import SettingsTab from './SettingsTab'; +import integrationConfig from '../../../../../config/integrations'; +import { RequestStatus } from '../../../../../utils/consts'; +import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../../../server/utils/constants'; + +@connect( + 'user,session,usbPorts,rflinkPath,rflinkStatus,getRflinkUsbPortStatus,getCurrentRflinkPathStatus,rflinkGetStatusStatus,rflinkFailed,rflinkDisconnectStatus,connectRflinkStatus,RflinkConnectionInProgress', + actions +) +class RflinkSettingsPage extends Component { + rflinkReadyListener = () => this.props.getStatus(); + rflinkFailedListener = () => this.props.rflinkFailed(); + + componentWillMount() { + this.props.getUsbPorts(); + this.props.getStatus(); + this.props.getCurrentRflinkPath(); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_READY, this.rflinkReadyListener); + this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_FAILED, this.rflinkFailedListener); + } + + componentWillUnmount() { + this.props.session.dispatcher.removeListener(WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_READY, this.rflinkReadyListener); + this.props.session.dispatcher.removeListener( + WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_FAILED, + this.rflinkFailedListener + ); + } + + render(props, {}) { + const loading = + props.getRflinkUsbPortStatus === RequestStatus.Getting || + props.getCurrentRflinkPathStatus === RequestStatus.Getting || + props.rflinkGetStatusStatus === RequestStatus.Getting || + props.rflinkDisconnectStatus === RequestStatus.Getting || + props.connectRflinkStatus === RequestStatus.Getting; + + return ( + + + + ); + } +} + +export default RflinkSettingsPage; From 51b82771457f7b0cdac88c4a90a44771b0251ee6 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 8 Feb 2020 15:38:58 +0100 Subject: [PATCH 003/236] debug --- server/services/rflink/index.js | 36 ++++++++++--------- .../rflink/lib/commands/rflink.connect.js | 13 ++++++- .../rflink/lib/events/rflink.addDevice.js | 10 ++++-- .../rflink/lib/events/rflink.message.js | 6 ++-- server/services/rflink/lib/index.js | 5 ++- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 237a965fc4..20c5d228af 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -4,28 +4,33 @@ const RflinkController = require('./api/rflink.controller'); const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); -let rfLinkManager; - module.exports = function RfLink(gladys, serviceId) { - - const Serialport = require('serialport'); - const Readline = require('@serialport/parser-readline'); + + +/** + * @description function to solve problems with rflinkpath = undefined + * @example + * init() + */ + + const rfLinkManager = new RfLinkManager(gladys, serviceId); + + /** * @description start rflink module * @example * gladys.services.rflink.start(); */ async function start() { - logger.log('Starting Rflink service'); const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); - - if (!RflinkPath) { + if (RflinkPath === undefined || !RflinkPath) { + logger.log('rflink service cannot start because the usb path is undefined'); throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } else { + logger.log('Starting Rflink service'); } - const port = new Serialport(RflinkPath, {baudRate : 57600}); - const readline = new Readline(); - port.pipe(readline); - rfLinkManager = new RfLinkManager(readline, gladys, serviceId); + + if (rfLinkManager === undefined) { throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); } else { @@ -45,14 +50,13 @@ module.exports = function RfLink(gladys, serviceId) { logger.log('Stopping Rflink service'); rfLinkManager.disconnect(); } - return Object.freeze({ start, stop, device : rfLinkManager, controllers : RflinkController(gladys, rfLinkManager, serviceId), - - }) - ; + }); + + }; diff --git a/server/services/rflink/lib/commands/rflink.connect.js b/server/services/rflink/lib/commands/rflink.connect.js index 7a569f9e26..729aa5d1eb 100644 --- a/server/services/rflink/lib/commands/rflink.connect.js +++ b/server/services/rflink/lib/commands/rflink.connect.js @@ -1,4 +1,6 @@ const os = require('os'); +const Serialport = require('serialport'); +const Readline = require('@serialport/parser-readline'); const logger = require('../../../../utils/logger'); /** @@ -8,13 +10,22 @@ const logger = require('../../../../utils/logger'); * rflink.connect(Path); */ function connect(Path) { - logger.debug(`Rflink : Connecting to USB = ${Path}`); // special case for macOS if (os.platform() === 'darwin') { this.Path = Path.replace('/dev/tty.', '/dev/cu.'); } else { this.Path = Path; } + + + const port = new Serialport(this.Path, {baudRate : 57600}); + const readline = new Readline(); + port.pipe(readline); + this.usb = readline; + + + logger.debug(`Rflink : Connecting to USB = ${Path}`); + this.connected = true; this.listen(); diff --git a/server/services/rflink/lib/events/rflink.addDevice.js b/server/services/rflink/lib/events/rflink.addDevice.js index 504d24b77f..e8b29e75f6 100644 --- a/server/services/rflink/lib/events/rflink.addDevice.js +++ b/server/services/rflink/lib/events/rflink.addDevice.js @@ -11,13 +11,17 @@ const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants */ function addDevice(device) { const id = device.external_id.split(':')[1]; - this.gladys.event.emit(EVENTS.DEVICE.NEW, { - device - }); + + + this.gladys.event.emit(EVENTS.DEVICE.NEW, + device, + ); + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, payload : device, }); + this.device[id] = device; diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 1acf504f7f..2c0d977de0 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -232,8 +232,8 @@ function message(msgRF) { read_only : false, keep_history: true, has_feedback: false, - min: 'OFF', - max: 'ON', + min: 0, + max: 1, }); @@ -289,8 +289,8 @@ function message(msgRF) { } - + this.addDevice(newDevice); } else if (doesntExistYet === false) { diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index b22177ca45..b4dfcf3497 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -1,4 +1,3 @@ -const logger = require('../../../utils/logger'); // Events @@ -20,12 +19,12 @@ const { getDevices } = require('./commands/rflink.getDevices'); -const RFlinkManager = function RFlinkManager(usb, gladys, serviceId) { - this.usb = usb; +const RFlinkManager = function RFlinkManager(gladys, serviceId) { this.gladys = gladys; this.scanInProgress = false; this.serviceId = serviceId; this.connected = false; + this.scanInProgress = false; this.device = {}; From dc350185cbb84b8f433dff7c887a8c7c462bb3f3 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 8 Feb 2020 19:57:58 +0100 Subject: [PATCH 004/236] add milight functions --- .../services/rflink/api/rflink.controller.js | 28 ++++++++++++++++++- .../lib/commands/rflink.milight.pair.js | 15 ++++++++++ .../lib/commands/rflink.milight.unpair.js | 13 +++++++++ .../rflink/lib/events/rflink.message.js | 1 + server/services/rflink/lib/index.js | 4 +++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 server/services/rflink/lib/commands/rflink.milight.pair.js create mode 100644 server/services/rflink/lib/commands/rflink.milight.unpair.js diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js index e59aee8b58..074fc3784b 100644 --- a/server/services/rflink/api/rflink.controller.js +++ b/server/services/rflink/api/rflink.controller.js @@ -9,7 +9,6 @@ module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { * @apiGroup RFlink */ async function getDevices(req, res) { - logger.log('getting devices ...'); res.json(RFlinkManager.getDevices()); } @@ -50,7 +49,34 @@ module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { }); } + /** + * @api {get} /api/v1/service/rflink/pair send a milight pairing comand + * @apiName pair + * @apiGroup RFlink + */ + async function pair(req, res) { + RFlinkManager.pair(); + } + + /** + * @api {get} /api/v1/service/rflink/pair send a milight unpairing comand + * @apiName unpair + * @apiGroup RFlink + */ + async function unpair(req, res) { + RFlinkManager.unpair(); +} + return { + 'get /api/v1/service/rflink/pair' : { + authenticated: true, + controller: asyncMiddleware(pair) + }, + 'get /api/v1/service/rflink/unpair' : { + authenticated: true, + controller: asyncMiddleware(unpair) + }, + 'get /api/v1/service/rflink/devices' : { authenticated: true, controller: asyncMiddleware(getDevices) diff --git a/server/services/rflink/lib/commands/rflink.milight.pair.js b/server/services/rflink/lib/commands/rflink.milight.pair.js new file mode 100644 index 0000000000..3557fdd7de --- /dev/null +++ b/server/services/rflink/lib/commands/rflink.milight.pair.js @@ -0,0 +1,15 @@ + + +/** + * @description pair a milight device + * @example + * rflink.pair() + */ +function pair() { + this.usb.write(''); + +} + +module.exports = { + pair, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/commands/rflink.milight.unpair.js b/server/services/rflink/lib/commands/rflink.milight.unpair.js new file mode 100644 index 0000000000..98d61cbd68 --- /dev/null +++ b/server/services/rflink/lib/commands/rflink.milight.unpair.js @@ -0,0 +1,13 @@ +/** + * @description unpair a milight device + * @example + * rflink.unpair() + */ +function unpair() { + this.usb.write(''); + +} + +module.exports = { + unpair, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 2c0d977de0..bfde196685 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -33,6 +33,7 @@ function message(msgRF) { selector : `rflink:${msg.id}`, external_id: `rflink:${msg.id}`, model : `${msg.protocol}`, + should_poll : false, features : [] }; diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index b4dfcf3497..0ef2217c39 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -14,6 +14,8 @@ const { connect } = require('./commands/rflink.connect'); const { disconnect } = require('./commands/rflink.disconnect'); const { listen } = require('./commands/rflink.listen'); const { getDevices } = require('./commands/rflink.getDevices'); +const { pair } = require('./commands/rflink.milight.pair'); +const { unpair } = require('./commands/rflink.milight.unpair'); @@ -45,6 +47,8 @@ RFlinkManager.prototype.connect = connect; RFlinkManager.prototype.disconnect = disconnect; RFlinkManager.prototype.listen = listen; RFlinkManager.prototype.getDevices = getDevices; +RFlinkManager.prototype.pair = pair; +RFlinkManager.prototype.unpair = unpair; From f2a92a8edb09738299e3ad2b86c07a55b64ea3d6 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 8 Feb 2020 19:58:38 +0100 Subject: [PATCH 005/236] add rflink settings --- .../all/rflink/device-page/Device.jsx | 6 ++--- .../all/rflink/device-page/DeviceForm.jsx | 10 ++++---- .../all/rflink/device-page/actions.js | 4 ++-- .../all/rflink/device-page/index.js | 1 + .../all/rflink/settings-page/SettingsTab.jsx | 23 +++++++++++++++++++ .../all/rflink/settings-page/actions.js | 17 ++++++++++++++ 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/front/src/routes/integration/all/rflink/device-page/Device.jsx b/front/src/routes/integration/all/rflink/device-page/Device.jsx index 2858204096..e91e4810eb 100644 --- a/front/src/routes/integration/all/rflink/device-page/Device.jsx +++ b/front/src/routes/integration/all/rflink/device-page/Device.jsx @@ -29,7 +29,7 @@ class RflinkDeviceBox extends Component { return (
-
{props.device.name || }
+
{props.device.name || }
diff --git a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx index af01ba5b9b..62e6253b7f 100644 --- a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx @@ -21,7 +21,7 @@ class RflinkDeviceForm extends Component {
} + placeholder={} />
} + /> + +
+
+ + +
+
+ +
+ {props.device && + props.device.features && + props.device.features.map(feature => ( + + +
+ +
+
+ ))} + {(!props.device.features || props.device.features.length === 0) && ( + + )} +
+
- + + +
diff --git a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx index 7aca5746c5..c6f4e5a1e8 100644 --- a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx @@ -1,11 +1,11 @@ import { Text, Localizer } from 'preact-i18n'; import cx from 'classnames'; -import style from './style.css'; import { RequestStatus } from '../../../../../utils/consts'; import Device from './Device'; +import style from './style.css'; -const DeviceTab = ({ children, ...props }) => ( +const NodeTab = ({ children, ...props }) => (

@@ -33,6 +33,9 @@ const DeviceTab = ({ children, ...props }) => ( />

+
@@ -43,12 +46,17 @@ const DeviceTab = ({ children, ...props }) => ( >
+ {props.rflinkDevices && props.rflinkDevices.length === 0 && ( +
+ +
+ )} {props.getRflinkDevicesStatus === RequestStatus.Getting &&
}
- {props.rflink && - props.rflink.map((device, index) => ( + {props.rflinkDevices && + props.rflinkDevices.map((rflinkDevice, index) => ( ( deleteDevice={props.deleteDevice} /> ))} - {props.rflink && props.rflink.length === 0 && ( - - )}
@@ -66,4 +71,4 @@ const DeviceTab = ({ children, ...props }) => (
); -export default DeviceTab; +export default NodeTab; diff --git a/front/src/routes/integration/all/rflink/device-page/actions.js b/front/src/routes/integration/all/rflink/device-page/actions.js index b6b1bb4eae..107c7af7d2 100644 --- a/front/src/routes/integration/all/rflink/device-page/actions.js +++ b/front/src/routes/integration/all/rflink/device-page/actions.js @@ -1,14 +1,13 @@ import { RequestStatus } from '../../../../../utils/consts'; import update from 'immutability-helper'; +import uuid from 'uuid'; import createActionsHouse from '../../../../../actions/house'; -import createActionsIntegration from '../../../../../actions/integration'; import debounce from 'debounce'; function createActions(store) { const houseActions = createActionsHouse(store); - const integrationActions = createActionsIntegration(store); const actions = { - async getRflinkDevices(state) { + async getRflinkDevices(state, take, skip) { store.setState({ getRflinkDevicesStatus: RequestStatus.Getting }); @@ -16,73 +15,50 @@ function createActions(store) { const options = { service: 'rflink', order_dir: state.getRflinkDeviceOrderDir || 'asc', - take: 10000, - skip: 0 + take, + skip }; if (state.rflinkDeviceSearch && state.rflinkDeviceSearch.length) { options.search = state.rflinkDeviceSearch; } - const rflinkDevices = await state.httpClient.get('/api/v1/service/rflink/device', options); - const rflinkDevicesMap = new Map(); - rflinkDevices.forEach(device => rflinkDevicesMap.set(device.external_id, device)); + const rflinkDevicesReceived = await state.httpClient.get('/api/v1/service/rflink/device', options); + let rflinkDevices; + if (skip === 0) { + rflinkDevices = rflinkDevicesReceived; + } else { + rflinkDevices = update(state.rflinkDevices, { + $push: rflinkDevicesReceived + }); + } store.setState({ rflinkDevices, - rflinkDevicesMap, getRflinkDevicesStatus: RequestStatus.Success }); - actions.getRflinkNewDevices(store.getState()); } catch (e) { store.setState({ getRflinkDevicesStatus: RequestStatus.Error }); } }, - async getRflinkNewDevices(state) { - store.setState({ - getRflinkNewDevicesStatus: RequestStatus.Getting - }); - try { - const rflinkNewDevices = await state.httpClient.get('/api/v1/service/rflink/devices'); - const rflinkNewDevicesFiltered = rflinkNewDevices.filter(device => { - if (!state.rflinkDevicesMap) { - return true; + addDevice(state) { + const uniqueId = uuid.v4(); + const rflinkDevices = update(state.rflinkDevices, { + $push: [ + { + id: uniqueId, + name: null, + should_poll: false, + service_id: state.currentIntegration.id, + external_id: 'rflink:' } - return !state.rflinkDevicesMap.has(device.external_id); - }); - store.setState({ - rflinkNewDevices: rflinkNewDevicesFiltered, - getRflinkNewDevicesStatus: RequestStatus.Success - }); - } catch (e) { - store.setState({ - getRflinkNewDevicesStatus: RequestStatus.Error - }); - } - }, - async saveDevice(state, device, index) { - const savedDevice = await state.httpClient.post('/api/v1/device', device); - const newState = update(state, { - rflinkDevices: { - $splice: [[index, 1, savedDevice]] - } + ] }); - store.setState(newState); - }, - async createDevice(state, device) { store.setState({ - getRflinkCreateDeviceStatus: RequestStatus.Getting + rflinkDevices }); - try { - await state.httpClient.post('/api/v1/device', device); - store.setState({ - getRflinkCreateDeviceStatus: RequestStatus.Success - }); - actions.getRflinkDevices(store.getState()); - } catch (e) { - store.setState({ - getRflinkCreateDeviceStatus: RequestStatus.Error - }); - } + }, + async saveDevice(state, device) { + await state.httpClient.post('/api/v1/device', device); }, updateDeviceProperty(state, index, property, value) { const newState = update(state, { @@ -109,17 +85,17 @@ function createActions(store) { store.setState({ rflinkDeviceSearch: e.target.value }); - await actions.getRflinkDevices(store.getState()); + await actions.getRflinkDevices(store.getState(), 20, 0); }, async changeOrderDir(state, e) { store.setState({ getRflinkDeviceOrderDir: e.target.value }); - await actions.getRflinkDevices(store.getState()); + await actions.getRflinkDevices(store.getState(), 20, 0); } }; actions.debouncedSearch = debounce(actions.search, 200); - return Object.assign({}, houseActions, integrationActions, actions); + return Object.assign({}, houseActions, actions); } export default createActions; diff --git a/front/src/routes/integration/all/rflink/device-page/index.js b/front/src/routes/integration/all/rflink/device-page/index.js index 191fa99f2d..cc40157393 100644 --- a/front/src/routes/integration/all/rflink/device-page/index.js +++ b/front/src/routes/integration/all/rflink/device-page/index.js @@ -1,27 +1,21 @@ import { Component } from 'preact'; import { connect } from 'unistore/preact'; import actions from './actions'; +import RflinkPage from '../RflinkPage'; import DevicePage from './DevicePage'; -import FoundDevices from './FoundDevices'; -import RflinkPage from '../RflinkPage.jsx'; +import integrationConfig from '../../../../../config/integrations'; -@connect( - 'session,user,rflinksDevices,houses,getRflinkDevicesStatus,rflinkNewDevices,getRflinkCreateDeviceStatus,getRflinkNewDevicesStatus', - actions -) +@connect('session,user,rflinkDevices,houses,getRflinkDevicesStatus', actions) class RflinkDevicePage extends Component { componentWillMount() { - this.props.getRflinkDevices(); + this.props.getRflinkDevices(20, 0); this.props.getHouses(); - this.props.getRflinkNewDevices(); - this.props.getIntegrationByName('rflink'); } render(props, {}) { return ( - - {props.rflinkDevices && props.rflinkDevices.length ? :
} - + + ); } diff --git a/front/src/routes/integration/all/rflink/device-page/style.css b/front/src/routes/integration/all/rflink/device-page/style.css index 67713e8fb7..1b4343b7c4 100644 --- a/front/src/routes/integration/all/rflink/device-page/style.css +++ b/front/src/routes/integration/all/rflink/device-page/style.css @@ -1,3 +1,3 @@ .emptyDiv { - min-height: 200px + min-height: 200px; } diff --git a/front/src/routes/integration/all/rflink/edit-page/index.js b/front/src/routes/integration/all/rflink/edit-page/index.js new file mode 100644 index 0000000000..1e75b36360 --- /dev/null +++ b/front/src/routes/integration/all/rflink/edit-page/index.js @@ -0,0 +1,20 @@ +import { Component } from 'preact'; +import { connect } from 'unistore/preact'; +// import actions from '../actions'; +import RflinkPage from '../RflinkPage'; +import UpdateDevice from '../../../../../components/device'; + +const RFLINK_PAGE_PATH = '/dashboard/integration/device/rflink'; + +@connect('user,session,httpClient,currentIntegration,houses', {}) +class EditRflinkDevice extends Component { + render(props, {}) { + return ( + + + + ); + } +} + +export default EditRflinkDevice; diff --git a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx index 5c44c8d788..0df9a9b5f1 100644 --- a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx +++ b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx @@ -1,8 +1,10 @@ import { Text } from 'preact-i18n'; import get from 'get-value'; import cx from 'classnames'; +import { STATE } from '../../../../../../../server/utils/constants'; const SettingsTab = ({ children, ...props }) => ( +
@@ -85,7 +87,23 @@ const SettingsTab = ({ children, ...props }) => (

+
+
+
+
+
+ + + + +
+ @@ -94,8 +112,9 @@ const SettingsTab = ({ children, ...props }) => (
+
+
-
); export default SettingsTab; diff --git a/front/src/routes/integration/all/rflink/settings-page/actions.js b/front/src/routes/integration/all/rflink/settings-page/actions.js index b41b236bbf..4e3d4d8cc4 100644 --- a/front/src/routes/integration/all/rflink/settings-page/actions.js +++ b/front/src/routes/integration/all/rflink/settings-page/actions.js @@ -90,6 +90,7 @@ const actions = store => { } catch (e) { } + }, async getStatus(state) { store.setState({ @@ -97,12 +98,15 @@ const actions = store => { }); try { const rflinkStatus = await state.httpClient.get('/api/v1/service/rflink/status'); - + if (rflinkStatus.currentMilightGateway.name === undefined) { + rflinkStatus.currentMilightGateway.name = 'error'; + } store.setState({ rflinkStatus, rflinkConnectionInProgress: false, rflinkGetStatusStatus: RequestStatus.Success }); + return rflinkStatus; } catch (e) { store.setState({ rflinkGetStatusStatus: RequestStatus.Error, From 24f2b077206ed681f865601848957ce57941d7c9 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 16 Feb 2020 07:35:53 +0100 Subject: [PATCH 008/236] add rflink cover --- front/src/assets/integrations/cover/rflink.png | Bin 0 -> 113621 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 front/src/assets/integrations/cover/rflink.png diff --git a/front/src/assets/integrations/cover/rflink.png b/front/src/assets/integrations/cover/rflink.png new file mode 100644 index 0000000000000000000000000000000000000000..0500ddf70c414cdeb4ee93079ee8bcd9bd542d7e GIT binary patch literal 113621 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 Date: Sun, 16 Feb 2020 07:37:16 +0100 Subject: [PATCH 009/236] show the service in UI --- front/src/config/integrations/device.en.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/front/src/config/integrations/device.en.json b/front/src/config/integrations/device.en.json index c332fecb7c..af1c94abb1 100644 --- a/front/src/config/integrations/device.en.json +++ b/front/src/config/integrations/device.en.json @@ -34,5 +34,11 @@ "name": "Sonoff", "description": "Control your Sonoff devices.", "img": "/assets/integrations/cover/sonoff.jpg" + }, + { + "key": "rflink", + "name": "rflink", + "description": "Control your rflink devices.", + "img": "/assets/integrations/cover/rflink.png" } ] From 9a6b62bc89bb734a6be20f506d96cd4967e03566 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 19 Feb 2020 09:34:05 +0100 Subject: [PATCH 010/236] Add files via upload --- .../services/rflink/api/rflink.controller.js | 38 ++++++++++++++++--- .../rflink/api/rflink.parse.ObjToRF.js | 8 ++-- server/services/rflink/index.js | 22 ++++++----- .../rflink/lib/commands/rflink.connect.js | 1 + .../rflink/lib/commands/rflink.disconnect.js | 1 + .../lib/commands/rflink.milight.pair.js | 8 ++-- .../lib/commands/rflink.milight.unpair.js | 7 +++- .../rflink/lib/events/rflink.connected.js | 1 + server/services/rflink/lib/index.js | 1 + 9 files changed, 64 insertions(+), 23 deletions(-) diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js index ed36163ff1..5cdbeeb8d8 100644 --- a/server/services/rflink/api/rflink.controller.js +++ b/server/services/rflink/api/rflink.controller.js @@ -18,6 +18,7 @@ module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { * @apiGroup RFlink */ async function connect(req, res) { + logger.log('Rflink connect'); const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); if (!rflinkPath) { throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); @@ -47,26 +48,49 @@ module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { currentMilightGateway : RFlinkManager.currentMilightGateway, connected: RFlinkManager.connected, scanInProgress: RFlinkManager.scanInProgress, + ready: RFlinkManager.ready, }); } + /** - * @api {get} /api/v1/service/rflink/pair send a milight pairing comand + * @api {get} /api/v1/service/rflink/pair send a milight pairing command * @apiName pair * @apiGroup RFlink */ async function pair(req, res) { - RFlinkManager.pair(); + logger.log('Milight pair'); + let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY'); + if (currentMilightGateway === null) { + currentMilightGateway = 'F746'; + } + RFlinkManager.pair(currentMilightGateway); } /** - * @api {get} /api/v1/service/rflink/pair send a milight unpairing comand + * @api {get} /api/v1/service/rflink/pair send a milight unpairing command * @apiName unpair * @apiGroup RFlink */ - async function unpair(req, res) { - RFlinkManager.unpair(); +async function unpair(req, res) { + logger.log('Milight unpair'); + let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY'); + if (currentMilightGateway === null) { + currentMilightGateway = 'F746'; + } + RFlinkManager.unpair(currentMilightGateway); } + /** + * @apiName unpair + * @apiGroup RFlink + * @api {post} /api/v1/service/rflink/remove remove a device from the device list + */ + async function remove(req, res) { + logger.log(req); + res.json({ + success : true, + }); + } return { 'get /api/v1/service/rflink/pair' : { @@ -93,6 +117,10 @@ module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { authenticated: true, controller: asyncMiddleware(getStatus) }, + 'get /api/v1/sevice/rflink/remove/' : { + authenticated: true, + controller: asyncMiddleware(remove) + }, }; }; \ No newline at end of file diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js index fbed29285f..c12604bd04 100644 --- a/server/services/rflink/api/rflink.parse.ObjToRF.js +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -11,10 +11,12 @@ const { WEBSOCKET_MESSAGE_TYPES } = require('../../../utils/constants'); */ function ObjToRF(device, deviceFeature, state) { const id = device.external_id.split(':')[1]; + const channel = device.external_id.split(':')[2]; - let Rfcode = `10;${device.protocol};${id};`; - if (device.channel !== undefined) { - Rfcode += `${device.channel};`; + let Rfcode = `10;${device.model};${id};`; + + if (channel !== undefined) { + Rfcode += `${channel};`; } if (state !== undefined) { diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 31bd1149a9..bef8723738 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -7,11 +7,6 @@ const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); module.exports = function RfLink(gladys, serviceId) { -/** - * @description function to solve problems with rflinkpath = undefined - * @example - * init() - */ const rfLinkManager = new RfLinkManager(gladys, serviceId); @@ -24,7 +19,6 @@ module.exports = function RfLink(gladys, serviceId) { async function start() { const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); if (RflinkPath === undefined || !RflinkPath) { - logger.log('rflink service cannot start because the usb path is undefined'); throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); } else { logger.log('Starting Rflink service'); @@ -36,8 +30,17 @@ module.exports = function RfLink(gladys, serviceId) { } else { rfLinkManager.connect(RflinkPath); } - const milightGateway = await gladys.variable.getValue('MILIGHT_GATEWAY', serviceId); - rfLinkManager.currentMilightGateway.name = milightGateway; + let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceId); + if (currentMilightGateway === null) { + currentMilightGateway = 'F746'; + } + if (rfLinkManager.currentMilightGateway.name === null || rfLinkManager.currentMilightGateway.name === undefined) { + currentMilightGateway = rfLinkManager.currentMilightGateway.name; + rfLinkManager.currentMilightGateway.name = currentMilightGateway; + } + + + } @@ -52,13 +55,12 @@ module.exports = function RfLink(gladys, serviceId) { logger.log('Stopping Rflink service'); rfLinkManager.disconnect(); } + return Object.freeze({ start, stop, device : rfLinkManager, controllers : RflinkController(gladys, rfLinkManager, serviceId), }); - - }; diff --git a/server/services/rflink/lib/commands/rflink.connect.js b/server/services/rflink/lib/commands/rflink.connect.js index 729aa5d1eb..bce073c998 100644 --- a/server/services/rflink/lib/commands/rflink.connect.js +++ b/server/services/rflink/lib/commands/rflink.connect.js @@ -27,6 +27,7 @@ function connect(Path) { logger.debug(`Rflink : Connecting to USB = ${Path}`); this.connected = true; + this.ready = true; this.listen(); } diff --git a/server/services/rflink/lib/commands/rflink.disconnect.js b/server/services/rflink/lib/commands/rflink.disconnect.js index 4844c21f3b..b2b8f0f415 100644 --- a/server/services/rflink/lib/commands/rflink.disconnect.js +++ b/server/services/rflink/lib/commands/rflink.disconnect.js @@ -12,6 +12,7 @@ function disconnect() { logger.debug('Rflink: Not connected, disconnecting'); } this.connected = false; + this.ready = false; this.scanInProgress = false; } diff --git a/server/services/rflink/lib/commands/rflink.milight.pair.js b/server/services/rflink/lib/commands/rflink.milight.pair.js index 6eb9fb71b0..7806a0d389 100644 --- a/server/services/rflink/lib/commands/rflink.milight.pair.js +++ b/server/services/rflink/lib/commands/rflink.milight.pair.js @@ -2,11 +2,13 @@ /** * @description pair a milight device + * @param {string} currentMilightGateway - Milight gateway. * @example * rflink.pair() */ -function pair() { +function pair(currentMilightGateway) { let number = '01'; + this.currentMilightGateway.name = currentMilightGateway; if (this.currentMilightGateway.name !== undefined && this.currentMilightGateway.number !==undefined) { if (this.currentMilightGateway.number < 10) { @@ -14,8 +16,8 @@ function pair() { } else { number = `${this.currentMilightGateway.number}`; } - - this.usb.write(`10;MiLightv1;${this.currentMilightGateway};${number};34BC;PAIR;`); + const msg = `10;MiLightv1;${this.currentMilightGateway};${number};34BC;PAIR;`; + this.usb.write(msg); } } diff --git a/server/services/rflink/lib/commands/rflink.milight.unpair.js b/server/services/rflink/lib/commands/rflink.milight.unpair.js index 35ff791315..09adc45d27 100644 --- a/server/services/rflink/lib/commands/rflink.milight.unpair.js +++ b/server/services/rflink/lib/commands/rflink.milight.unpair.js @@ -1,10 +1,12 @@ /** * @description unpair a milight device + * @param {string} currentMilightGateway - Milight gateway. * @example * rflink.unpair() */ -function unpair() { +function unpair(currentMilightGateway) { let number = '01'; + this.currentMilightGateway.name = currentMilightGateway; if (this.currentMilightGateway.name !== undefined && this.currentMilightGateway.number !==undefined) { if (this.currentMilightGateway.number < 10) { @@ -13,7 +15,8 @@ function unpair() { number = `${this.currentMilightGateway.number}`; } - this.usb.write(`10;MiLightv1;${this.currentMilightGateway};${number};34BC;UNPAIR;`); + const msg = `10;MiLightv1;${this.currentMilightGateway};${number};34BC;UNPAIR;`; + this.usb.write(msg); } // else { // show a message in setup tab to tell user that gatewa y is undefined // } diff --git a/server/services/rflink/lib/events/rflink.connected.js b/server/services/rflink/lib/events/rflink.connected.js index 9770f9359f..277069cc16 100644 --- a/server/services/rflink/lib/events/rflink.connected.js +++ b/server/services/rflink/lib/events/rflink.connected.js @@ -9,6 +9,7 @@ const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants function connected() { logger.debug(`Rflink : Gateway is connected`); this.connected = true; + this.ready = true; this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, payload: {}, diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index 5291c2fe68..96f23060bd 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -25,6 +25,7 @@ const RFlinkManager = function RFlinkManager(gladys, serviceId) { this.scanInProgress = false; this.serviceId = serviceId; this.connected = false; + this.ready = false; this.scanInProgress = false; this.device = {}; this.currentMilightGateway = {}; From dc4b5ccb795c296f1d91b3ff025e486f4efe4874 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 19 Feb 2020 09:35:23 +0100 Subject: [PATCH 011/236] front update --- .../integration/all/rflink/RflinkPage.jsx | 4 ++-- .../all/rflink/device-page/DevicePage.jsx | 3 --- .../all/rflink/device-page/actions.js | 21 +++---------------- .../all/rflink/device-page/index.js | 2 +- .../integration/all/rflink/edit-page/index.js | 2 +- .../all/rflink/settings-page/actions.js | 19 ++++++++++++++++- .../all/rflink/settings-page/index.js | 5 ++++- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/front/src/routes/integration/all/rflink/RflinkPage.jsx b/front/src/routes/integration/all/rflink/RflinkPage.jsx index 220bd39255..1194294789 100644 --- a/front/src/routes/integration/all/rflink/RflinkPage.jsx +++ b/front/src/routes/integration/all/rflink/RflinkPage.jsx @@ -24,14 +24,14 @@ const RflinkPage = ({ children, ...props }) => ( - +
diff --git a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx index c6f4e5a1e8..6bfcdebec4 100644 --- a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx @@ -33,9 +33,6 @@ const NodeTab = ({ children, ...props }) => ( />
-
diff --git a/front/src/routes/integration/all/rflink/device-page/actions.js b/front/src/routes/integration/all/rflink/device-page/actions.js index 107c7af7d2..5d2681d472 100644 --- a/front/src/routes/integration/all/rflink/device-page/actions.js +++ b/front/src/routes/integration/all/rflink/device-page/actions.js @@ -1,6 +1,5 @@ import { RequestStatus } from '../../../../../utils/consts'; import update from 'immutability-helper'; -import uuid from 'uuid'; import createActionsHouse from '../../../../../actions/house'; import debounce from 'debounce'; @@ -40,23 +39,6 @@ function createActions(store) { }); } }, - addDevice(state) { - const uniqueId = uuid.v4(); - const rflinkDevices = update(state.rflinkDevices, { - $push: [ - { - id: uniqueId, - name: null, - should_poll: false, - service_id: state.currentIntegration.id, - external_id: 'rflink:' - } - ] - }); - store.setState({ - rflinkDevices - }); - }, async saveDevice(state, device) { await state.httpClient.post('/api/v1/device', device); }, @@ -74,6 +56,9 @@ function createActions(store) { }, async deleteDevice(state, device, index) { await state.httpClient.delete('/api/v1/device/' + device.selector); + await state.httpClient.post('/api/v1/service/rflink/remove/', { + external_id : device.selector, + }); const newState = update(state, { rflinkDevices: { $splice: [[index, 1]] diff --git a/front/src/routes/integration/all/rflink/device-page/index.js b/front/src/routes/integration/all/rflink/device-page/index.js index cc40157393..aae4b828bc 100644 --- a/front/src/routes/integration/all/rflink/device-page/index.js +++ b/front/src/routes/integration/all/rflink/device-page/index.js @@ -5,7 +5,7 @@ import RflinkPage from '../RflinkPage'; import DevicePage from './DevicePage'; import integrationConfig from '../../../../../config/integrations'; -@connect('session,user,rflinkDevices,houses,getRflinkDevicesStatus', actions) +@connect('session,user,rflinkDevices,houses,getRflinkDevicesStatus,currentIntegration', actions) class RflinkDevicePage extends Component { componentWillMount() { this.props.getRflinkDevices(20, 0); diff --git a/front/src/routes/integration/all/rflink/edit-page/index.js b/front/src/routes/integration/all/rflink/edit-page/index.js index 1e75b36360..3c0e8b18c1 100644 --- a/front/src/routes/integration/all/rflink/edit-page/index.js +++ b/front/src/routes/integration/all/rflink/edit-page/index.js @@ -11,7 +11,7 @@ class EditRflinkDevice extends Component { render(props, {}) { return ( - + ); } diff --git a/front/src/routes/integration/all/rflink/settings-page/actions.js b/front/src/routes/integration/all/rflink/settings-page/actions.js index 4e3d4d8cc4..e0d46b563a 100644 --- a/front/src/routes/integration/all/rflink/settings-page/actions.js +++ b/front/src/routes/integration/all/rflink/settings-page/actions.js @@ -59,6 +59,17 @@ const actions = store => { }); } }, + async getMilightInput(state) { + try { + const currentMilightGateway = await state.httpClient.get('/api/v1/service/rflink/variable/CURRENT_MILIGHT_GATEWAY'); + store.setState({ + currentMilightGateway : currentMilightGateway.value, + }); + } catch (e) { + + } + + }, async disconnect(state) { store.setState({ rflinkDisconnectStatus: RequestStatus.Getting @@ -77,6 +88,9 @@ const actions = store => { }, async pair(state) { try { + await state.httpClient.post('/api/v1/service/rflink/variable/CURRENT_MILIGHT_GATEWAY', { + value: state.currentMilightGateway + }); await state.httpClient.post('/api/v1/service/rflink/pair'); } catch (e) { @@ -85,6 +99,9 @@ const actions = store => { }, async unpair(state) { try { + await state.httpClient.post('/api/v1/service/rflink/variable/CURRENT_MILIGHT_GATEWAY', { + value: state.currentMilightGateway + }); await state.httpClient.post('/api/v1/service/rflink/unpair'); } catch (e) { @@ -99,7 +116,7 @@ const actions = store => { try { const rflinkStatus = await state.httpClient.get('/api/v1/service/rflink/status'); if (rflinkStatus.currentMilightGateway.name === undefined) { - rflinkStatus.currentMilightGateway.name = 'error'; + rflinkStatus.currentMilightGateway = 'error'; } store.setState({ rflinkStatus, diff --git a/front/src/routes/integration/all/rflink/settings-page/index.js b/front/src/routes/integration/all/rflink/settings-page/index.js index 596363ba45..90800e55e2 100644 --- a/front/src/routes/integration/all/rflink/settings-page/index.js +++ b/front/src/routes/integration/all/rflink/settings-page/index.js @@ -8,7 +8,7 @@ import { RequestStatus } from '../../../../../utils/consts'; import { WEBSOCKET_MESSAGE_TYPES } from '../../../../../../../server/utils/constants'; @connect( - 'user,session,usbPorts,rflinkPath,rflinkStatus,getRflinkUsbPortStatus,getCurrentRflinkPathStatus,rflinkGetStatusStatus,rflinkFailed,rflinkDisconnectStatus,connectRflinkStatus,RflinkConnectionInProgress', + 'user,session,usbPorts,rflinkPath,rflinkStatus,getRflinkUsbPortStatus,getCurrentRflinkPathStatus,rflinkGetStatusStatus,rflinkFailed,rflinkDisconnectStatus,connectRflinkStatus,RflinkConnectionInProgress,currentMilightGateway', actions ) class RflinkSettingsPage extends Component { @@ -19,6 +19,9 @@ class RflinkSettingsPage extends Component { this.props.getUsbPorts(); this.props.getStatus(); this.props.getCurrentRflinkPath(); + this.props.pair(); + this.props.unpair(); + this.props.getMilightInput(); this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_READY, this.rflinkReadyListener); this.props.session.dispatcher.addListener(WEBSOCKET_MESSAGE_TYPES.RFLINK.DRIVER_FAILED, this.rflinkFailedListener); } From 24d26790e66b195a77cc41905f5aedf7543bb644 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 20 Feb 2020 09:35:45 +0100 Subject: [PATCH 012/236] Update index.js --- server/services/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/services/index.js b/server/services/index.js index de065fb04b..0590b62506 100644 --- a/server/services/index.js +++ b/server/services/index.js @@ -8,3 +8,4 @@ module.exports.usb = require('./usb'); module.exports.xiaomi = require('./xiaomi'); module.exports.zwave = require('./zwave'); module.exports.sonoff = require('./sonoff'); +module.exports.rflink = require('./rflink'); From 469c0a3de721dca93edc3e2517a2ad0e24fe1169 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 20 Feb 2020 17:54:03 +0100 Subject: [PATCH 013/236] Add milight in settings --- .../all/rflink/settings-page/SettingsTab.jsx | 20 +++++++--- .../all/rflink/settings-page/actions.js | 37 +++++++++++-------- .../all/rflink/settings-page/index.js | 5 +-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx index 0df9a9b5f1..f5e0f984cb 100644 --- a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx +++ b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx @@ -92,17 +92,27 @@ const SettingsTab = ({ children, ...props }) => (
-
+ +
- - + + +
+
+ + +
diff --git a/front/src/routes/integration/all/rflink/device-page/actions.js b/front/src/routes/integration/all/rflink/device-page/actions.js index 5d2681d472..a115ab9e20 100644 --- a/front/src/routes/integration/all/rflink/device-page/actions.js +++ b/front/src/routes/integration/all/rflink/device-page/actions.js @@ -2,8 +2,11 @@ import { RequestStatus } from '../../../../../utils/consts'; import update from 'immutability-helper'; import createActionsHouse from '../../../../../actions/house'; import debounce from 'debounce'; +import createActionsIntegration from '../../../../../actions/integration'; function createActions(store) { + const integrationActions = createActionsIntegration(store); + store.setState({integrationActions}); const houseActions = createActionsHouse(store); const actions = { async getRflinkDevices(state, take, skip) { @@ -80,7 +83,7 @@ function createActions(store) { } }; actions.debouncedSearch = debounce(actions.search, 200); - return Object.assign({}, houseActions, actions); + return Object.assign({}, houseActions, integrationActions ,actions); } export default createActions; diff --git a/front/src/routes/integration/all/rflink/device-page/index.js b/front/src/routes/integration/all/rflink/device-page/index.js index aae4b828bc..2e1dc43175 100644 --- a/front/src/routes/integration/all/rflink/device-page/index.js +++ b/front/src/routes/integration/all/rflink/device-page/index.js @@ -12,6 +12,8 @@ class RflinkDevicePage extends Component { this.props.getHouses(); } + + render(props, {}) { return ( @@ -20,5 +22,6 @@ class RflinkDevicePage extends Component { ); } } +console.log(this.props); export default RflinkDevicePage; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx new file mode 100644 index 0000000000..c3a89b4582 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx @@ -0,0 +1,153 @@ +import { Text, Localizer } from 'preact-i18n'; +import { Component } from 'preact'; +import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_UNITS } from '../../../../../../../../server/utils/constants'; +import { DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; +import get from 'get-value'; + +const RflinkFeatureBox = ({ children, ...props }) => { + return ( +
+
+
+ + +
+
+
+ + + } + /> + +
+ +
+ + + } + /> + +
+ + {props.feature.category === DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR && ( +
+ + + + +
+ )} + +
+ + + } + /> + +
+
+ + + } + /> + +
+ +
+ +
+
+
+
+ ); +}; + +class RflinkFeatureBoxComponent extends Component { + updateName = e => { + this.props.updateFeatureProperty(e, 'name', this.props.featureIndex); + }; + updateExternalId = e => { + this.props.updateFeatureProperty(e, 'external_id', this.props.featureIndex); + }; + updateMin = e => { + this.props.updateFeatureProperty(e, 'min', this.props.featureIndex); + }; + updateMax = e => { + this.props.updateFeatureProperty(e, 'max', this.props.featureIndex); + }; + updateUnit = e => { + this.props.updateFeatureProperty(e, 'unit', this.props.featureIndex); + }; + deleteFeature = () => { + this.props.deleteFeature(this.props.featureIndex); + }; + render() { + return ( + + ); + } +} + +export default RflinkFeatureBoxComponent; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx new file mode 100644 index 0000000000..82e7d49aca --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx @@ -0,0 +1,105 @@ +import { Text, MarkupText } from 'preact-i18n'; +import { Link } from 'preact-router/match'; +import Feature from './Feature'; +import { DEVICE_FEATURE_CATEGORIES_LIST } from '../../../../../../../../server/utils/constants'; +import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; +import RflinkDeviceForm from '../DeviceForm'; +import cx from 'classnames'; + +const FeatureTab = ({ children, ...props }) => ( +
+
+ + + +

+ {(props.device && props.device.name) || } +

+
+
+
+
+
+
+ +
+ {props.saveStatus === RequestStatus.Error && ( +
+ +
+ )} + {props.saveStatus === RequestStatus.ConflictError && ( +
+ +
+ )} + {!props.loading && !props.device && ( +
+

+ +

+ + + +
+ )} + {props.device && ( +
+ + +
+ + +
+ +
+ {props.device && + props.device.features.map((feature, index) => ( + + ))} +
+ +
+ + + + +
+
+ )} +
+
+
+
+); + +export default FeatureTab; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/index.js b/front/src/routes/integration/all/rflink/device-page/setup/index.js new file mode 100644 index 0000000000..a3101c0291 --- /dev/null +++ b/front/src/routes/integration/all/rflink/device-page/setup/index.js @@ -0,0 +1,190 @@ +import { Component } from 'preact'; +import { connect } from 'unistore/preact'; +import actions from '../actions'; +import FeatureTab from './FeatureTab'; +import RflinkPage from '../../RflinkPage'; +import integrationConfig from '../../../../../../config/integrations'; +import uuid from 'uuid'; +import get from 'get-value'; +import update from 'immutability-helper'; +import { RequestStatus } from '../../../../../../utils/consts'; + + +@connect('session,user,httpClient,houses,currentIntegration', actions) +class RflinkDeviceSetupPage extends Component { + selectFeature(e) { + this.setState({ + selectedFeature: e.target.value + }); + } + + addFeature() { + const featureData = this.state.selectedFeature.split('|'); + + + const device = update(this.state.device, { + features: { + $push: [ + { + id: uuid.v4(), + category: featureData[0], + external_id: 'rflink:', + type: featureData[1], + read_only: true, + has_feedback: false, + keep_history: true + } + ] + } + }); + + this.setState({ + device, + selectedFeature: undefined + }); + } + + deleteFeature(featureIndex) { + const device = update(this.state.device, { + features: { + $splice: [[featureIndex, 1]] + } + }); + + this.setState({ + device + }); + } + + updateDeviceProperty(deviceIndex, property, value) { + const device = update(this.state.device, { + [property]: { + $set: value + } + }); + + this.setState({ + device + }); + } + + updateFeatureProperty(e, property, featureIndex) { + let value = e.target.value; + if (property === 'external_id' && !value.startsWith('rflink:')) { + if (value.length < 5) { + value = 'rflink:'; + } else { + value = `rflink:${value}`; + } + } + const device = update(this.state.device, { + features: { + [featureIndex]: { + [property]: { + $set: value + } + } + } + }); + + this.setState({ + device + }); + } + + async saveDevice() { + this.setState({ + loading: true + }); + try { + const device = await this.props.httpClient.post('/api/v1/device', this.state.device); + this.setState({ + saveStatus: RequestStatus.Success, + loading: false, + device + }); + } catch (e) { + const status = get(e, 'response.status'); + if (status === 409) { + this.setState({ + saveStatus: RequestStatus.ConflictError, + loading: false + }); + } else { + this.setState({ + saveStatus: RequestStatus.Error, + loading: false + }); + } + } + } + + constructor(props) { + super(props); + + this.state = { + loading: true + }; + + this.selectFeature = this.selectFeature.bind(this); + this.addFeature = this.addFeature.bind(this); + this.deleteFeature = this.deleteFeature.bind(this); + this.updateDeviceProperty = this.updateDeviceProperty.bind(this); + this.updateFeatureProperty = this.updateFeatureProperty.bind(this); + this.saveDevice = this.saveDevice.bind(this); + } + + async componentWillMount() { + this.props.getHouses(); + await this.props.getIntegrationByName('rflink'); + + let { deviceSelector } = this.props; + let device; + + if (!deviceSelector) { + const uniqueId = uuid.v4(); + device = { + id: uniqueId, + name: null, + should_poll: false, + external_id: uniqueId, + service_id: this.props.currentIntegration.id, + features: [] + }; + } else { + const loadedDevice = await this.props.httpClient.get(`/api/v1/device/${deviceSelector}`); + + if ( + loadedDevice && + this.props.currentIntegration && + loadedDevice.service_id === this.props.currentIntegration.id + ) { + device = loadedDevice; + } + } + + this.setState({ + device, + loading: false + }); + } + + render(props, state) { + return ( + + + + ); + } +} + +export default RflinkDeviceSetupPage; From 6b515c629ad86902a11d89d87488025cf4b8143d Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 8 Mar 2020 11:50:38 +0100 Subject: [PATCH 022/236] Creating devices update --- server/services/rflink/lib/commands/rflink.milight.pair.js | 6 ++++++ .../services/rflink/lib/commands/rflink.milight.unpair.js | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/server/services/rflink/lib/commands/rflink.milight.pair.js b/server/services/rflink/lib/commands/rflink.milight.pair.js index 7d9fffd497..54741a7043 100644 --- a/server/services/rflink/lib/commands/rflink.milight.pair.js +++ b/server/services/rflink/lib/commands/rflink.milight.pair.js @@ -26,6 +26,12 @@ function pair(currentMilightGateway, milightZone) { this.sendUsb.write(msg, error => { logger.log(error); }); + this.sendUsb.write(msg, error => { + logger.log(error); + }); + this.sendUsb.write(msg, error => { + logger.log(error); + }); newLight = { diff --git a/server/services/rflink/lib/commands/rflink.milight.unpair.js b/server/services/rflink/lib/commands/rflink.milight.unpair.js index 4547615aec..354d76b276 100644 --- a/server/services/rflink/lib/commands/rflink.milight.unpair.js +++ b/server/services/rflink/lib/commands/rflink.milight.unpair.js @@ -15,6 +15,12 @@ function unpair(currentMilightGateway, milightZone) { this.sendUsb.write(msg, error => { logger.log(error); }); + this.sendUsb.write(msg, error => { + logger.log(error); + }); + this.sendUsb.write(msg, error => { + logger.log(error); + }); } // else { // show a message in setup tab to tell user that gatewa y is undefined // } From ad893da88543021cb3c77ccb7546aae80cc262e1 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 8 Mar 2020 11:52:57 +0100 Subject: [PATCH 023/236] constants update --- server/utils/compare.js | 31 +++++++++++++++++++++++++++++++ server/utils/constants.js | 11 +++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 server/utils/compare.js diff --git a/server/utils/compare.js b/server/utils/compare.js new file mode 100644 index 0000000000..72b8fd6f3d --- /dev/null +++ b/server/utils/compare.js @@ -0,0 +1,31 @@ +/** + * @description Compare two values with one operator. + * @param {string} operator - The comparaison operator. + * @param {*} a - The first value. + * @param {*} b - The second value. + * @returns {boolean} Return true or false. + * @example + * const result = compare('=', 1, 2); // return false. + */ +function compare(operator, a, b) { + switch (operator) { + case '=': + return a === b; + case '<': + return a < b; + case '>': + return a > b; + case '<=': + return a <= b; + case '>=': + return a >= b; + case '!=': + return a !== b; + default: + throw new Error(`Operator ${operator} not found`); + } +} + +module.exports = { + compare, +}; diff --git a/server/utils/constants.js b/server/utils/constants.js index adab398e9d..35b1a17046 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -88,6 +88,9 @@ const EVENTS = { HUE_CHANGED: 'light.hue-changed', SATURATION_CHANGED: 'light.saturation-changed', }, + TRIGGERS: { + CHECK: 'trigger.check', + }, TEMPERATURE_SENSOR: { TEMPERATURE_CHANGED: 'temperature.changed', }, @@ -184,8 +187,8 @@ const ACTIONS = { SCENE: { START: 'scene.start', }, - TELEGRAM: { - SEND: 'telegram.send', + MESSAGE: { + SEND: 'message.send', }, }; @@ -236,6 +239,7 @@ const DEVICE_FEATURE_TYPES = { COLOR: 'color', TEMPERATURE: 'temperature', POWER: 'power', + MODE: 'mode', }, SENSOR: { DECIMAL: 'decimal', @@ -350,6 +354,9 @@ const WEBSOCKET_MESSAGE_TYPES = { CONNECTED: 'mqtt.connected', ERROR: 'mqtt.error', }, + RFLINK : { + DRIVER_READY: 'rflink.driver-ready', + }, XIAOMI: { NEW_DEVICE: 'xiaomi.new-device', }, From f075ae481c7cba8640f5618ec92ee288a80d1d2a Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 8 Mar 2020 14:38:18 +0100 Subject: [PATCH 024/236] Device creation finished --- .../all/rflink/device-page/DeviceForm.jsx | 26 +++++++++++++++++++ .../rflink/device-page/setup/FeatureTab.jsx | 10 +++++++ 2 files changed, 36 insertions(+) diff --git a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx index 62e6253b7f..4e275e2156 100644 --- a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx @@ -1,6 +1,7 @@ import { Text, Localizer } from 'preact-i18n'; import { Component } from 'preact'; import { DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; +import { DEVICE_MODELS_LIST } from '../../../../../../../server/utils/constants'; import get from 'get-value'; class RflinkDeviceForm extends Component { @@ -12,6 +13,10 @@ class RflinkDeviceForm extends Component { this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); }; + updateModel = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'model', e.target.value); + }; + updateExternalId = e => { this.props.updateDeviceProperty(this.props.deviceIndex, 'external_id', e.target.value); }; @@ -55,6 +60,21 @@ class RflinkDeviceForm extends Component { ))}
+
+ + +
+ + + + + +
); } diff --git a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx index 82e7d49aca..56bd33e003 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx @@ -77,6 +77,16 @@ const FeatureTab = ({ children, ...props }) => (
+ + + + + + + + + +
{props.device && props.device.features.map((feature, index) => ( From 5c67ae550a5905bb3269dcf5c2419eca13307779 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 8 Mar 2020 14:38:43 +0100 Subject: [PATCH 025/236] Constants update --- server/utils/constants.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/server/utils/constants.js b/server/utils/constants.js index 35b1a17046..1b485660ee 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -228,6 +228,34 @@ const DEVICE_FEATURE_CATEGORIES = { CUBE: 'cube', BUTTON: 'button', UNKNOWN: 'unknown', +}; + +const DEVICE_MODELS = { + TRISTATE: 'Tristate', + KAKU: 'Kaku', + NEWKAKU: 'NewKaku', + HOMEEASY: 'HomeEasy', + CONRAD: 'Conrad RSL2', + BLYSS: 'Blyss', + RTS: 'RTS', + AB400D: 'AB400D', + IMPULS: 'Impuls', + EURODOMEST: 'EuroDomest', + X10: 'X10', + HOMECOMFORT: 'HomeConfort', + KOPPLA: 'IKEA Koppla', + CHUANGO: 'Chuango', + SELECTPLUS: 'Selectplus', + DELTRONIC: 'Deltronic', + MERTIK: 'Mertik', + EV1527 : 'EV1527', + + + + + + + }; const DEVICE_FEATURE_TYPES = { @@ -409,6 +437,7 @@ const SESSION_TOKEN_TYPE_LIST = createList(SESSION_TOKEN_TYPES); const DEVICE_FEATURE_UNITS_LIST = createList(DEVICE_FEATURE_UNITS); const DASHBOARD_TYPE_LIST = createList(DASHBOARD_TYPE); const DASHBOARD_BOX_TYPE_LIST = createList(DASHBOARD_BOX_TYPE); +const DEVICE_MODELS_LIST = createList(DEVICE_MODELS); module.exports.STATE = STATE; module.exports.EVENTS = EVENTS; @@ -433,6 +462,7 @@ module.exports.DEVICE_FEATURE_TYPES_LIST = DEVICE_FEATURE_TYPES_LIST; module.exports.USER_ROLE_LIST = USER_ROLE_LIST; module.exports.AVAILABLE_LANGUAGES_LIST = AVAILABLE_LANGUAGES_LIST; module.exports.SESSION_TOKEN_TYPE_LIST = SESSION_TOKEN_TYPE_LIST; +module.exports.DEVICE_MODELS_LIST = DEVICE_MODELS_LIST; module.exports.DEVICE_POLL_FREQUENCIES = DEVICE_POLL_FREQUENCIES; module.exports.DEVICE_POLL_FREQUENCIES_LIST = createList(DEVICE_POLL_FREQUENCIES); From 06bd0400ca288bb9df607dbccb1f4eed71d1cebb Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sun, 8 Mar 2020 17:22:04 +0100 Subject: [PATCH 026/236] bug fixes --- .../integration/all/rflink/device-page/DeviceForm.jsx | 4 +++- .../routes/integration/all/rflink/device-page/actions.js | 1 + .../integration/all/rflink/device-page/setup/index.js | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx index 4e275e2156..739a697c33 100644 --- a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx @@ -60,6 +60,7 @@ class RflinkDeviceForm extends Component { ))}
+ {props.device.model !== 'milight' &&
- + }
+ +
+ + + + + +
+
+

+ +

+
+
+ +
+

+ { + + `> ${get(props, 'rflinkStatus.lastCommand')} \n` + } +

+
+
+
); From 3ef7020937f3ae4f9fd24a4f4227ba622bb293bb Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 09:15:56 +0100 Subject: [PATCH 030/236] i18n upd --- front/src/config/i18n/en.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 598a2b32c8..a79145361a 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -188,21 +188,30 @@ "connectedWithSuccess": "Rflink Gateway connected with success.", "connecting": "Trying to connect to Rflink Gateway ...", "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", + "debug" : { + "title" : "Rflink debug console", + "info" : "Here you can see the last command that Rflink sent to Gladys" + + }, "milight" : { "title" : "Rflink Milight settings", "gatewayBarinfo" : "Gateway number: ", "zoneInfo" : "Gateway zone: ", - "about" : " You can use your actual milight bridge id or if you don't have a gateway , use a new one (F746 by default) the id is just a code to identify the gateway . For the moment you can use unlimited milight but with one bridge", + "about" : " You can use your actual milight bridge id or if you don't have a gateway , use a new one (F746 by default) the id is just a code to identify the gateway. You can use unlimited milight but each bridge has 4 zones", "pairButton" : "Pair", "unpairButton" : "Unpair" } }, "feature": { "nameLabel": "Name", + "model": "Model", "namePlaceholder": "Enter feature name", - "externalIdLabel": "Feature external ID", - "externalIdMessage": "The feature external ID is an unique ID which helps you identify this feature outside of Gladys when sending a Rflink message. It musts start with \"rflink:\". Read the Rflink API documentation here.", - "externalIdPlaceholder": "Feature RFlink message key", + "switchIdLabel": "Switch ID", + "switchIdMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", + "switchIdPlaceholder": "Switch id", + "switchNumberLabel": "Switch number", + "switchNumberMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", + "switchNumberPlaceholder": "Switch number", "unitLabel": "Unit", "minLabel": "Minimum value", "minPlaceholder": "Enter feature minimum value", From 50d7d4d7a432000a55844912dc374d3092378040 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 09:17:13 +0100 Subject: [PATCH 031/236] constants upd --- server/utils/constants.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/utils/constants.js b/server/utils/constants.js index 1b485660ee..7e451aace2 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -233,22 +233,22 @@ const DEVICE_FEATURE_CATEGORIES = { const DEVICE_MODELS = { TRISTATE: 'Tristate', KAKU: 'Kaku', - NEWKAKU: 'NewKaku', - HOMEEASY: 'HomeEasy', - CONRAD: 'Conrad RSL2', + NEWKAKU: 'Newkaku', + HOMEEASY: 'Homeeasy', + CONRAD: 'Conrad rsl2', BLYSS: 'Blyss', - RTS: 'RTS', - AB400D: 'AB400D', + RTS: 'Rts', + AB400D: 'Ab400d', IMPULS: 'Impuls', - EURODOMEST: 'EuroDomest', + EURODOMEST: 'Eurodomest', X10: 'X10', - HOMECOMFORT: 'HomeConfort', - KOPPLA: 'IKEA Koppla', + HOMECOMFORT: 'Homeconfort', + KOPPLA: 'Ikea koppla', CHUANGO: 'Chuango', SELECTPLUS: 'Selectplus', DELTRONIC: 'Deltronic', MERTIK: 'Mertik', - EV1527 : 'EV1527', + EV1527 : 'Ev1527', From 8d07fdb850b642e967e2d9e882fc35d89e2d85cd Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 09:21:05 +0100 Subject: [PATCH 032/236] i18n upd --- front/src/config/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index a79145361a..28bc231931 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -190,7 +190,7 @@ "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", "debug" : { "title" : "Rflink debug console", - "info" : "Here you can see the last command that Rflink sent to Gladys" + "info" : "Here you can see the last command that Rflink sent to Gladys \n it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ..." }, "milight" : { From 2edead30b1610167b0172338a527e8731bc3539a Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 09:57:35 +0100 Subject: [PATCH 033/236] Add files via upload --- .../integration/all/rflink/device-page/setup/Feature.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx index 0db65d563b..c6e9888890 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx @@ -31,7 +31,7 @@ const RflinkFeatureBox = ({ children, ...props }) => { - {(props.feature.type ==='binary' && props.feature.category === 'switch') && ( // Switch +
+
- + {(props.feature.read_only === false || props.feature.read_only === undefined) && ( // Switch
- + )} @@ -159,6 +160,7 @@ class RflinkFeatureBoxComponent extends Component { this.props.updateFeatureProperty(e, 'unit', this.props.featureIndex); }; updateSwitchId = e => { + console.log(this.props); this.props.feature.switchId = e.target.value; let external = { target : { From 48020266ac9db30c4fa710b715087b0892a924b8 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 09:59:21 +0100 Subject: [PATCH 034/236] change labels --- front/src/config/i18n/en.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 28bc231931..b358f96cf3 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -190,7 +190,7 @@ "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", "debug" : { "title" : "Rflink debug console", - "info" : "Here you can see the last command that Rflink sent to Gladys \n it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ..." + "info" : "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ..." }, "milight" : { @@ -206,12 +206,12 @@ "nameLabel": "Name", "model": "Model", "namePlaceholder": "Enter feature name", - "switchIdLabel": "Switch ID", + "switchIdLabel": "ID ", "switchIdMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", - "switchIdPlaceholder": "Switch id", - "switchNumberLabel": "Switch number", + "switchIdPlaceholder": "Rflink device ID", + "switchNumberLabel": "Channel", "switchNumberMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", - "switchNumberPlaceholder": "Switch number", + "switchNumberPlaceholder": "value of SWITCH in debug console (settings tab) ", "unitLabel": "Unit", "minLabel": "Minimum value", "minPlaceholder": "Enter feature minimum value", From 9395609afc8c0d2b51bd03cb19abef1f3341c2f8 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 16:59:27 +0100 Subject: [PATCH 035/236] send debug command --- .../services/rflink/api/rflink.controller.js | 18 ++++++++++++++++++ .../rflink/lib/commands/rflink.milight.pair.js | 3 --- .../rflink/lib/events/rflink.message.js | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js index af0c2a4d4d..5d1551abc6 100644 --- a/server/services/rflink/api/rflink.controller.js +++ b/server/services/rflink/api/rflink.controller.js @@ -100,7 +100,21 @@ async function unpair(req, res) { currentMilightGateway, milightZone }); + } + /** + * @api {get} /api/v1/service/rflink/debug send a rflink command + * @apiName debug + * @apiGroup RFlink + */ + async function sendDebug(req, res) { + const command = req.body.value; + RFlinkManager.sendUsb.write(command); + + res.json({ + succes : true, + }); + } /** * @apiName unpair * @apiGroup RFlink @@ -134,6 +148,10 @@ async function unpair(req, res) { authenticated: true, controller: asyncMiddleware(disconnect) }, + 'post /api/v1/service/rflink/debug' : { + authenticated: true, + controller: asyncMiddleware(sendDebug) + }, 'get /api/v1/service/rflink/status' : { authenticated: true, controller: asyncMiddleware(getStatus) diff --git a/server/services/rflink/lib/commands/rflink.milight.pair.js b/server/services/rflink/lib/commands/rflink.milight.pair.js index 54741a7043..633eaac350 100644 --- a/server/services/rflink/lib/commands/rflink.milight.pair.js +++ b/server/services/rflink/lib/commands/rflink.milight.pair.js @@ -24,13 +24,10 @@ function pair(currentMilightGateway, milightZone) { // } const msg = `10;MiLightv1;${this.currentMilightGateway};0${number};34BC;PAIR;`; this.sendUsb.write(msg, error => { - logger.log(error); }); this.sendUsb.write(msg, error => { - logger.log(error); }); this.sendUsb.write(msg, error => { - logger.log(error); }); diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 88bb21dc1e..6dce9b555c 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -15,6 +15,7 @@ const { * rflink.message(msg); */ function message(msgRF) { + console.log(msgRF); this.lastCommand = msgRF; const msg = RFtoObj(msgRF); let newDevice; From b180e5d3c09fc79167c8e2cb43bdfecb3dc5b9d9 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 17:00:29 +0100 Subject: [PATCH 036/236] front send debug --- .../all/rflink/settings-page/SettingsTab.jsx | 13 +++++++++++++ .../integration/all/rflink/settings-page/actions.js | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx index 81988adb35..0157e9e81b 100644 --- a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx +++ b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx @@ -145,6 +145,19 @@ const SettingsTab = ({ children, ...props }) => ( }

+
+ +
+ +
+
diff --git a/front/src/routes/integration/all/rflink/settings-page/actions.js b/front/src/routes/integration/all/rflink/settings-page/actions.js index 2a4cbd6e46..6357fc054e 100644 --- a/front/src/routes/integration/all/rflink/settings-page/actions.js +++ b/front/src/routes/integration/all/rflink/settings-page/actions.js @@ -39,6 +39,9 @@ const actions = store => { RflinkPath: e.target.value }); }, + updateDebugCommand(state, e) { + store.setState({commandToSend : e.target.value}); + }, updateMilight(state, e) { store.setState({ currentMilightGateway: e.target.value @@ -113,6 +116,15 @@ const actions = store => { } }, + async sendDebug(state) { + try{ + await state.httpClient.post('/api/v1/service/rflink/debug', { + value: state.commandToSend + }); + } catch (e) { + + } + }, async getStatus(state) { store.setState({ rflinkGetStatusStatus: RequestStatus.Getting From 3792b8402beb8975816d097a1ffe47327f38f3b5 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Wed, 18 Mar 2020 17:00:55 +0100 Subject: [PATCH 037/236] i18n upd --- front/src/config/i18n/en.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index b358f96cf3..cbc682a573 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -190,7 +190,10 @@ "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", "debug" : { "title" : "Rflink debug console", - "info" : "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ..." + "info" : "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ...", + "placeholder" : "message to send", + "sendButton" : "Send" + }, "milight" : { From 7069a37c5c91fe7dcb9390e56d0adc6ea4d4be58 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:51:45 +0100 Subject: [PATCH 038/236] bug fixes --- .../rflink/api/rflink.parse.ObjToRF.js | 2 +- .../rflink/lib/commands/rflink.setValue.js | 36 +++++++++---------- server/services/rflink/lib/index.js | 2 -- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js index a7aa9376e4..f6cfa5c440 100644 --- a/server/services/rflink/api/rflink.parse.ObjToRF.js +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -24,7 +24,7 @@ function ObjToRF(device, deviceFeature, state) { if (state !== undefined) { Rfcode += `${state};`; } else { - this.error('NoState'); + logger.log('no state'); }; Rfcode += '\n'; diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js index d09b5eb126..d1e1c56474 100644 --- a/server/services/rflink/lib/commands/rflink.setValue.js +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -14,27 +14,27 @@ function setValue(device, deviceFeature, state) { let value; logger.log(device.external_id); - - if (state === 0 || state === false) { - value = 'OFF'; + value = state; + + if (deviceFeature.type === 'binary') { + switch (state) { + case 0: + case false : + value = 'OFF'; + break; + case 1: + case true : + value = 'ON'; + + break; + default : + value = state; + break; + + } } - switch (state) { - case 0: - case false : - value = 'OFF'; - break; - case 1: - case true : - value = 'ON'; - - break; - default : - value = state; - break; - - } diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index 34ad8c05ea..bddf6f4612 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -4,7 +4,6 @@ const { newValue } = require('./events/rflink.newValue'); const { addDevice } = require('./events/rflink.addDevice'); const { message } = require('./events/rflink.message.js'); -const { error } = require('./events/rflink.error'); // COMMANDS @@ -39,7 +38,6 @@ const RFlinkManager = function RFlinkManager(gladys, serviceId) { RFlinkManager.prototype.message = message; RFlinkManager.prototype.newValue = newValue; RFlinkManager.prototype.addDevice = addDevice; -RFlinkManager.prototype.error = error; // Commands From 6690e6c9238a42ec9c1ca0362d1d2c89246472d0 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:52:27 +0100 Subject: [PATCH 039/236] bug fixes --- .../integration/all/rflink/device-page/setup/FeatureTab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx index 56bd33e003..62fd707ffd 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx @@ -27,7 +27,7 @@ const FeatureTab = ({ children, ...props }) => (
- +
{props.saveStatus === RequestStatus.Error && (
From 8a2b539e682aed499ca633640068b7f96877cd1a Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:52:39 +0100 Subject: [PATCH 040/236] i18n upd --- front/src/config/i18n/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index cbc682a573..5e38b8dad9 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -208,6 +208,7 @@ "feature": { "nameLabel": "Name", "model": "Model", + "message" : "You can only create actuators , sensors are automatically detected and added in the device tab when they send messages", "namePlaceholder": "Enter feature name", "switchIdLabel": "ID ", "switchIdMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", From 3d187ac6f304109de805faeb2c32d5ebbfd08e30 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 14:53:19 +0100 Subject: [PATCH 041/236] add tests --- .../services/rflink/lib/devicesToTest.test.js | 33 ++++++++++ .../services/rflink/lib/rflinkManager.test.js | 62 +++++++++++++++++++ server/test/services/rflink/rflink.test.js | 4 +- .../test/services/rflink/rflinkMock.test.js | 1 - 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 server/test/services/rflink/lib/devicesToTest.test.js create mode 100644 server/test/services/rflink/lib/rflinkManager.test.js diff --git a/server/test/services/rflink/lib/devicesToTest.test.js b/server/test/services/rflink/lib/devicesToTest.test.js new file mode 100644 index 0000000000..642a32f30c --- /dev/null +++ b/server/test/services/rflink/lib/devicesToTest.test.js @@ -0,0 +1,33 @@ +const { + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES, + } = require('../../../../utils/constants'); + +const DEVICES = [ + { + service_id : 'a810b8db-6d04-4697-bed3-c4b72c996279', + name : `Prise `, + selector : `rflink:86aa7:11`, + external_id: `rflink:86aa7:11`, + model : 'Tristate', + should_poll : false, + features : [ + { + name : 'switch', + selector : `rflink:86aa7:switch:11`, + external_id : `rflink:86aa7:switch:11`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.SWITCH, + type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }, + ] + }, + {}, +]; + +module.exports = DEVICES; \ No newline at end of file diff --git a/server/test/services/rflink/lib/rflinkManager.test.js b/server/test/services/rflink/lib/rflinkManager.test.js new file mode 100644 index 0000000000..f54db90a27 --- /dev/null +++ b/server/test/services/rflink/lib/rflinkManager.test.js @@ -0,0 +1,62 @@ +/* eslint-disable no-restricted-syntax */ +const { expect } = require('chai'); +const { assert } = require('sinon'); +const EventEmitter = require('events'); +const {DEVICE_FEATURE_CATEGORIES} = require('../../../../utils/constants'); + + +const RflinkManager = require('../../../../services/rflink/lib'); +const DEVICES = require('./devicesToTest.test'); +const RflinkMock = require('../rflinkMock.test'); + +describe('Rflink Manager Commands', () => { + const gladys = { + event: new EventEmitter(), + }; + const rflinkManager = new RflinkManager(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279'); + rflinkManager.connected = true; + + it('should connect to the Rflink Gateway', () => { + rflinkManager.connect('COM8'); + }); + + it('should disconnect from the Rflink Gateway', () => { + rflinkManager.disconnect(); + }); + + it('should listen', () => { + rflinkManager.listen(); + }); + + it('should get devices', ()=> { + rflinkManager.getDevices(); + }); + + it('should pair Milights', () => { + rflinkManager.pair('F746', '1'); + }); + + it('should unpair Milights', () => { + rflinkManager.unpair('F746', '1'); + }); + + DEVICES.forEach((device) => { + device.features.forEach((feature) => { + if (feature !==undefined) { + it('should change value of a device', () => { + if (feature.read_only === false) { // it's not a sensor + if (feature.type === 'binary' || feature.type === 'decimal') { + return rflinkManager.setValue(device, feature, 1); + } + return 'error'; + } + }); + } + }); + }); + + + + + }); + diff --git a/server/test/services/rflink/rflink.test.js b/server/test/services/rflink/rflink.test.js index 88932615f4..aecfdd43e3 100644 --- a/server/test/services/rflink/rflink.test.js +++ b/server/test/services/rflink/rflink.test.js @@ -1,10 +1,12 @@ const { expect } = require('chai'); const EventEmitter = require('events'); const proxyquire = require('proxyquire').noCallThru(); +const SerialPort = require('serialport'); const RflinkMock = require('./rflinkMock.test'); + const RflinkService = proxyquire('../../../services/rflink/index', { - 'SerialPort': RflinkMock, + 'SerialPort': SerialPort, }); const gladys = { diff --git a/server/test/services/rflink/rflinkMock.test.js b/server/test/services/rflink/rflinkMock.test.js index 25873e86c4..e1f63608e6 100644 --- a/server/test/services/rflink/rflinkMock.test.js +++ b/server/test/services/rflink/rflinkMock.test.js @@ -8,7 +8,6 @@ Rflink.prototype = Object.create(new EventEmitter()); Rflink.prototype.message = fake.returns(null); Rflink.prototype.newValue = fake.returns(null); Rflink.prototype.addDevice = fake.returns(null); -Rflink.prototype.error = fake.returns(null); Rflink.prototype.setValue = fake.returns(null); Rflink.prototype.connect = fake.returns(null); Rflink.prototype.disconnect = fake.returns(null); From 107ebb89d6f63e620fadb9bb1b893a0a6e1ddfc4 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:25:09 +0100 Subject: [PATCH 042/236] prettier --- .../services/rflink/api/rflink.controller.js | 327 ++++---- .../rflink/api/rflink.parse.ObjToRF.js | 73 +- .../rflink/api/rflink.parse.RFtoObject.js | 73 +- server/services/rflink/index.js | 123 ++- .../rflink/lib/commands/rflink.connect.js | 110 ++- .../rflink/lib/commands/rflink.disconnect.js | 42 +- .../rflink/lib/commands/rflink.getDevices.js | 30 +- .../rflink/lib/commands/rflink.listen.js | 32 +- .../lib/commands/rflink.milight.pair.js | 221 +++-- .../lib/commands/rflink.milight.unpair.js | 64 +- .../rflink/lib/commands/rflink.setValue.js | 112 ++- .../rflink/lib/events/rflink.addDevice.js | 62 +- .../rflink/lib/events/rflink.connected.js | 42 +- .../rflink/lib/events/rflink.message.js | 765 ++++++++---------- .../rflink/lib/events/rflink.newValue.js | 16 +- server/services/rflink/lib/index.js | 109 +-- server/services/rflink/package.json | 43 +- 17 files changed, 1032 insertions(+), 1212 deletions(-) diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js index 5d1551abc6..f01dd3eafd 100644 --- a/server/services/rflink/api/rflink.controller.js +++ b/server/services/rflink/api/rflink.controller.js @@ -1,165 +1,162 @@ -/* eslint-disable prefer-destructuring */ -const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); -const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); -const logger = require('../../../utils/logger'); - -module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { - /** - * @api {get} /api/v1/service/rflink/devices get rflink devices - * @apiName getDevices - * @apiGroup RFlink - */ - async function getDevices(req, res) { - res.json(RFlinkManager.getDevices()); - } - - /** - * @api {get} /api/v1/service/rflink/connect connect to the gateway - * @apiName connect - * @apiGroup RFlink - */ - async function connect(req, res) { - const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); - if (!rflinkPath) { - throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); - } - RFlinkManager.connect(rflinkPath); - res.json({succes: true, }); - } - /** - * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway - * @apiName disconnect - * @apiGroup RFlink - */ - async function disconnect(req, res) { - RFlinkManager.disconnect(); - res.json({ - success: true, - }); - } - /** - * @api {get} /api/v1/service/rflink/status get the gateway's status - * @apiName getStatus - * @apiGroup RFlink - */ - async function getStatus(req, res) { - res.json({ - currentMilightGateway : RFlinkManager.currentMilightGateway, - lastCommand : RFlinkManager.lastCommand, - connected: RFlinkManager.connected, - scanInProgress: RFlinkManager.scanInProgress, - ready: RFlinkManager.ready, - }); - } - - - /** - * @api {get} /api/v1/service/rflink/pair send a milight pairing command - * @apiName pair - * @apiGroup RFlink - */ - async function pair(req, res) { - let milightZone = req.body.zone; - if (milightZone === undefined || milightZone === null) { - milightZone = 1; - } - let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceID); - if (currentMilightGateway === null) { - currentMilightGateway = RFlinkManager.currentMilightGateway; - } - RFlinkManager.currentMilightGateway = currentMilightGateway; - - RFlinkManager.pair(currentMilightGateway, milightZone); - - res.json({ - succes : true, - currentMilightGateway, - milightZone - }); - } - - /** - * @api {get} /api/v1/service/rflink/pair send a milight unpairing command - * @apiName unpair - * @apiGroup RFlink - */ -async function unpair(req, res) { - let milightZone = req.body.zone; - if (milightZone === undefined || milightZone === null) { - milightZone = 1; - } - let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceID); - if (currentMilightGateway === null) { - currentMilightGateway = RFlinkManager.currentMilightGateway; - } - RFlinkManager.currentMilightGateway = currentMilightGateway; - - RFlinkManager.unpair(currentMilightGateway, milightZone); - res.json({ - succes : true, - currentMilightGateway, - milightZone - }); - -} - /** - * @api {get} /api/v1/service/rflink/debug send a rflink command - * @apiName debug - * @apiGroup RFlink - */ - async function sendDebug(req, res) { - const command = req.body.value; - RFlinkManager.sendUsb.write(command); - - res.json({ - succes : true, - }); - } - /** - * @apiName unpair - * @apiGroup RFlink - * @api {post} /api/v1/service/rflink/remove remove a device from the device list - */ - async function remove(req, res) { - logger.log(req); - res.json({ - success : true, - }); - } - - return { - 'post /api/v1/service/rflink/pair' : { - authenticated: true, - controller: asyncMiddleware(pair) - }, - 'post /api/v1/service/rflink/unpair' : { - authenticated: true, - controller: asyncMiddleware(unpair) - }, - 'get /api/v1/service/rflink/devices' : { - authenticated: true, - controller: asyncMiddleware(getDevices) - }, - 'post /api/v1/service/rflink/connect' : { - authenticated: true, - controller: asyncMiddleware(connect) - }, - 'post /api/v1/service/rflink/disconnect' : { - authenticated: true, - controller: asyncMiddleware(disconnect) - }, - 'post /api/v1/service/rflink/debug' : { - authenticated: true, - controller: asyncMiddleware(sendDebug) - }, - 'get /api/v1/service/rflink/status' : { - authenticated: true, - controller: asyncMiddleware(getStatus) - }, - 'get /api/v1/service/rflink/remove' : { - authenticated: true, - controller: asyncMiddleware(remove) - }, - - }; -}; \ No newline at end of file +/* eslint-disable prefer-destructuring */ +const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); +const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); +const logger = require('../../../utils/logger'); + +module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { + /** + * @api {get} /api/v1/service/rflink/devices get rflink devices + * @apiName getDevices + * @apiGroup RFlink + */ + async function getDevices(req, res) { + res.json(RFlinkManager.getDevices()); + } + + /** + * @api {get} /api/v1/service/rflink/connect connect to the gateway + * @apiName connect + * @apiGroup RFlink + */ + async function connect(req, res) { + const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); + if (!rflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } + RFlinkManager.connect(rflinkPath); + res.json({ succes: true }); + } + /** + * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway + * @apiName disconnect + * @apiGroup RFlink + */ + async function disconnect(req, res) { + RFlinkManager.disconnect(); + res.json({ + success: true, + }); + } + /** + * @api {get} /api/v1/service/rflink/status get the gateway's status + * @apiName getStatus + * @apiGroup RFlink + */ + async function getStatus(req, res) { + res.json({ + currentMilightGateway: RFlinkManager.currentMilightGateway, + lastCommand: RFlinkManager.lastCommand, + connected: RFlinkManager.connected, + scanInProgress: RFlinkManager.scanInProgress, + ready: RFlinkManager.ready, + }); + } + + /** + * @api {get} /api/v1/service/rflink/pair send a milight pairing command + * @apiName pair + * @apiGroup RFlink + */ + async function pair(req, res) { + let milightZone = req.body.zone; + if (milightZone === undefined || milightZone === null) { + milightZone = 1; + } + let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceID); + if (currentMilightGateway === null) { + currentMilightGateway = RFlinkManager.currentMilightGateway; + } + RFlinkManager.currentMilightGateway = currentMilightGateway; + + RFlinkManager.pair(currentMilightGateway, milightZone); + + res.json({ + succes: true, + currentMilightGateway, + milightZone, + }); + } + + /** + * @api {get} /api/v1/service/rflink/pair send a milight unpairing command + * @apiName unpair + * @apiGroup RFlink + */ + async function unpair(req, res) { + let milightZone = req.body.zone; + if (milightZone === undefined || milightZone === null) { + milightZone = 1; + } + let currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceID); + if (currentMilightGateway === null) { + currentMilightGateway = RFlinkManager.currentMilightGateway; + } + RFlinkManager.currentMilightGateway = currentMilightGateway; + + RFlinkManager.unpair(currentMilightGateway, milightZone); + res.json({ + succes: true, + currentMilightGateway, + milightZone, + }); + } + /** + * @api {get} /api/v1/service/rflink/debug send a rflink command + * @apiName debug + * @apiGroup RFlink + */ + async function sendDebug(req, res) { + const command = req.body.value; + RFlinkManager.sendUsb.write(command); + + res.json({ + succes: true, + }); + } + /** + * @apiName unpair + * @apiGroup RFlink + * @api {post} /api/v1/service/rflink/remove remove a device from the device list + */ + async function remove(req, res) { + logger.log(req); + res.json({ + success: true, + }); + } + + return { + 'post /api/v1/service/rflink/pair': { + authenticated: true, + controller: asyncMiddleware(pair), + }, + 'post /api/v1/service/rflink/unpair': { + authenticated: true, + controller: asyncMiddleware(unpair), + }, + 'get /api/v1/service/rflink/devices': { + authenticated: true, + controller: asyncMiddleware(getDevices), + }, + 'post /api/v1/service/rflink/connect': { + authenticated: true, + controller: asyncMiddleware(connect), + }, + 'post /api/v1/service/rflink/disconnect': { + authenticated: true, + controller: asyncMiddleware(disconnect), + }, + 'post /api/v1/service/rflink/debug': { + authenticated: true, + controller: asyncMiddleware(sendDebug), + }, + 'get /api/v1/service/rflink/status': { + authenticated: true, + controller: asyncMiddleware(getStatus), + }, + 'get /api/v1/service/rflink/remove': { + authenticated: true, + controller: asyncMiddleware(remove), + }, + }; +}; diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js index f6cfa5c440..20569aabf3 100644 --- a/server/services/rflink/api/rflink.parse.ObjToRF.js +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -1,38 +1,35 @@ -const logger = require('../../../utils/logger'); - -// eslint-disable-next-line jsdoc/check-alignment -/** -* @description convert a rflink device object to a string that can be sent to rflink -* @param {Object} device - Secure node. -* @param {string} deviceFeature - The devicce feature. -* @param state - The state of the device. -* @example -* rflink.ObjToRF(device); -*/ -function ObjToRF(device, deviceFeature, state) { - const id = device.external_id.split(':')[1]; - const channel = device.external_id.split(':')[2]; - - let Rfcode = `10;${device.model};${id};`; - - if (channel !== undefined) { - Rfcode += `${channel};`; - } else { - logger.log('channel undefined'); - } - - if (state !== undefined) { - Rfcode += `${state};`; - } else { - logger.log('no state'); - }; - - Rfcode += '\n'; - - - - - return Rfcode; -}; - -module.exports = ObjToRF; \ No newline at end of file +const logger = require('../../../utils/logger'); + +// eslint-disable-next-line jsdoc/check-alignment +/** + * @description convert a rflink device object to a string that can be sent to rflink + * @param {Object} device - Secure node. + * @param {string} deviceFeature - The devicce feature. + * @param {any} state - The state of the device. + * @example + * rflink.ObjToRF(device); + */ +function ObjToRF(device, deviceFeature, state) { + const id = device.external_id.split(':')[1]; + const channel = device.external_id.split(':')[2]; + + let Rfcode = `10;${device.model};${id};`; + + if (channel !== undefined) { + Rfcode += `${channel};`; + } else { + logger.log('channel undefined'); + } + + if (state !== undefined) { + Rfcode += `${state};`; + } else { + logger.log('no state'); + } + + Rfcode += '\n'; + + return Rfcode; +} + +module.exports = ObjToRF; diff --git a/server/services/rflink/api/rflink.parse.RFtoObject.js b/server/services/rflink/api/rflink.parse.RFtoObject.js index 1888e66f7f..86c8ddeb8e 100644 --- a/server/services/rflink/api/rflink.parse.RFtoObject.js +++ b/server/services/rflink/api/rflink.parse.RFtoObject.js @@ -1,45 +1,28 @@ - - -/* eslint-disable prefer-destructuring */ -// eslint-disable-next-line jsdoc/check-alignment -/** -* @description convert a rflink message to an array. -* @param {string} data - Secure node. -* @returns {Object} Return an array with an index for each value. -* @example -* rflink.RftoObj(data); -*/ -function RfToObj(data) { - - const newDevice = {}; - const msg = String(data).split(';'); - - if(parseInt(msg[0], 10)===20 && parseInt(msg[1], 10)!==0 && msg[2]!=='OK' && msg[3] !== undefined){ - - newDevice.protocol = msg[2]; - newDevice.features = []; - for (let i = 3; i < msg.length ; i+=1) { - if (msg[i].includes('=') === true) { - const temp = msg[i].split('='); - newDevice[`${temp[0].toLowerCase()}`] = temp[1]; - } - } - - - - - } - - return newDevice; - - } - - - - - - - - - -module.exports = RfToObj ; \ No newline at end of file +/* eslint-disable prefer-destructuring */ +// eslint-disable-next-line jsdoc/check-alignment +/** + * @description convert a rflink message to an array. + * @param {string} data - Secure node. + * @returns {Object} Return an array with an index for each value. + * @example + * rflink.RftoObj(data); + */ +function RfToObj(data) { + const newDevice = {}; + const msg = String(data).split(';'); + + if (parseInt(msg[0], 10) === 20 && parseInt(msg[1], 10) !== 0 && msg[2] !== 'OK' && msg[3] !== undefined) { + newDevice.protocol = msg[2]; + newDevice.features = []; + for (let i = 3; i < msg.length; i += 1) { + if (msg[i].includes('=') === true) { + const temp = msg[i].split('='); + newDevice[`${temp[0].toLowerCase()}`] = temp[1]; + } + } + } + + return newDevice; +} + +module.exports = RfToObj; diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 030e748bec..370fc13e0b 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -1,68 +1,55 @@ -const logger = require('../../utils/logger'); -const RfLinkManager = require('./lib'); -const RflinkController = require('./api/rflink.controller'); -const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); - - -module.exports = function RfLink(gladys, serviceId) { - - - - const rfLinkManager = new RfLinkManager(gladys, serviceId); - - -/** - * @description start rflink module - * @example - * gladys.services.rflink.start(); - */ - async function start() { - const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); - - if (RflinkPath === undefined || !RflinkPath) { - throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); - } else { - logger.log('Starting Rflink service'); - } - - - if (rfLinkManager === undefined) { - throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); - } else { - try { - rfLinkManager.connect(RflinkPath); - } catch (err){ - Promise.reject(Error(err)); - } - - } - const currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceId); - rfLinkManager.currentMilightGateway = currentMilightGateway; - if (rfLinkManager.currentMilightGateway === null) { - rfLinkManager.currentMilightGateway = 'F746'; - } - - - - - } - - -/** - * @description stop rfllink module - * @example - * gladys.services.rflink.stop(); - */ - async function stop() { - logger.log('Stopping Rflink service'); - rfLinkManager.disconnect(); - } - - return Object.freeze({ - start, - stop, - device : rfLinkManager, - controllers : RflinkController(gladys, rfLinkManager, serviceId), - }); -}; - +const logger = require('../../utils/logger'); +const RfLinkManager = require('./lib'); +const RflinkController = require('./api/rflink.controller'); +const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); + +module.exports = function RfLink(gladys, serviceId) { + const rfLinkManager = new RfLinkManager(gladys, serviceId); + + /** + * @description start rflink module + * @example + * gladys.services.rflink.start(); + */ + async function start() { + const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); + + if (RflinkPath === undefined || !RflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } else { + logger.log('Starting Rflink service'); + } + + if (rfLinkManager === undefined) { + throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); + } else { + try { + rfLinkManager.connect(RflinkPath); + } catch (err) { + Promise.reject(Error(err)); + } + } + const currentMilightGateway = await gladys.variable.getValue('CURRENT_MILIGHT_GATEWAY', serviceId); + rfLinkManager.currentMilightGateway = currentMilightGateway; + if (rfLinkManager.currentMilightGateway === null) { + rfLinkManager.currentMilightGateway = 'F746'; + } + } + + /** + * @description stop rfllink module + * @example + * gladys.services.rflink.stop(); + */ + async function stop() { + logger.log('Stopping Rflink service'); + rfLinkManager.disconnect(); + } + + return Object.freeze({ + start, + stop, + device: rfLinkManager, + controllers: RflinkController(gladys, rfLinkManager, serviceId), + }); +}; diff --git a/server/services/rflink/lib/commands/rflink.connect.js b/server/services/rflink/lib/commands/rflink.connect.js index 0678b1ecb2..2c4c0cef2f 100644 --- a/server/services/rflink/lib/commands/rflink.connect.js +++ b/server/services/rflink/lib/commands/rflink.connect.js @@ -1,61 +1,49 @@ -const os = require('os'); -const Serialport = require('serialport'); -const Readline = require('@serialport/parser-readline'); -const logger = require('../../../../utils/logger'); - -/** - * @description Connect to Rflink - * @param {string} Path - Path to the Rflink gateway. - * @example - * rflink.connect(Path); - */ -function connect(Path) { - // special case for macOS - if (os.platform() === 'darwin') { - this.Path = Path.replace('/dev/tty.', '/dev/cu.'); - } else { - this.Path = Path; - } - - try { - const port = new Serialport(this.Path, { - baudRate : 57600, - dataBits : 8, - parity : 'none', - autoOpen : true, - - }); - - const readline = new Readline({ - baudRate : 57600, - }); - port.pipe(readline); - this.usb = readline; - this.sendUsb = port; - - logger.debug(`Rflink : Connecting to USB = ${Path}`); - - this.connected = true; - this.ready = true; - this.listen(); - - - } catch (error) { - this.connected = false; - this.ready = false; - this.scanInProgress = false; - - } - - - - - - - - } - - module.exports = { - connect, - }; - \ No newline at end of file +const os = require('os'); +const Serialport = require('serialport'); +const Readline = require('@serialport/parser-readline'); +const logger = require('../../../../utils/logger'); + +/** + * @description Connect to Rflink + * @param {string} Path - Path to the Rflink gateway. + * @example + * rflink.connect(Path); + */ +function connect(Path) { + // special case for macOS + if (os.platform() === 'darwin') { + this.Path = Path.replace('/dev/tty.', '/dev/cu.'); + } else { + this.Path = Path; + } + + try { + const port = new Serialport(this.Path, { + baudRate: 57600, + dataBits: 8, + parity: 'none', + autoOpen: true, + }); + + const readline = new Readline({ + baudRate: 57600, + }); + port.pipe(readline); + this.usb = readline; + this.sendUsb = port; + + logger.debug(`Rflink : Connecting to USB = ${Path}`); + + this.connected = true; + this.ready = true; + this.listen(); + } catch (error) { + this.connected = false; + this.ready = false; + this.scanInProgress = false; + } +} + +module.exports = { + connect, +}; diff --git a/server/services/rflink/lib/commands/rflink.disconnect.js b/server/services/rflink/lib/commands/rflink.disconnect.js index b2b8f0f415..a6110ac2c8 100644 --- a/server/services/rflink/lib/commands/rflink.disconnect.js +++ b/server/services/rflink/lib/commands/rflink.disconnect.js @@ -1,21 +1,21 @@ -const logger = require('../../../../utils/logger'); - -/** - * @description Disconnect Rflink Gateway. - * @example - * rflink.disconnect(); - */ -function disconnect() { - if (this.Path && this.connected) { - logger.debug(`Rflink : Disconnecting...`); - } else { - logger.debug('Rflink: Not connected, disconnecting'); - } - this.connected = false; - this.ready = false; - this.scanInProgress = false; -} - -module.exports = { - disconnect, -}; \ No newline at end of file +const logger = require('../../../../utils/logger'); + +/** + * @description Disconnect Rflink Gateway. + * @example + * rflink.disconnect(); + */ +function disconnect() { + if (this.Path && this.connected) { + logger.debug(`Rflink : Disconnecting...`); + } else { + logger.debug('Rflink: Not connected, disconnecting'); + } + this.connected = false; + this.ready = false; + this.scanInProgress = false; +} + +module.exports = { + disconnect, +}; diff --git a/server/services/rflink/lib/commands/rflink.getDevices.js b/server/services/rflink/lib/commands/rflink.getDevices.js index 440321353b..152696371b 100644 --- a/server/services/rflink/lib/commands/rflink.getDevices.js +++ b/server/services/rflink/lib/commands/rflink.getDevices.js @@ -1,16 +1,14 @@ - -/** - * @description return a list of the rflink devices - * @example - * rflink.getDevices(); - * @returns {Object} Devices. - */ -function getDevices () { - const devices = this.device; - return devices; - -} - -module.exports = { - getDevices, -}; \ No newline at end of file +/** + * @description return a list of the rflink devices + * @example + * rflink.getDevices(); + * @returns {Object} Devices. + */ +function getDevices() { + const devices = this.device; + return devices; +} + +module.exports = { + getDevices, +}; diff --git a/server/services/rflink/lib/commands/rflink.listen.js b/server/services/rflink/lib/commands/rflink.listen.js index f79bef4602..3494cc013c 100644 --- a/server/services/rflink/lib/commands/rflink.listen.js +++ b/server/services/rflink/lib/commands/rflink.listen.js @@ -1,18 +1,14 @@ - -/** - * @description listen - * @example - * rflink.listen(); - */ -function listen () { - this.usb.on('data', (data) => { - this.message(data); - - - - }); -} - -module.exports = { - listen, -}; \ No newline at end of file +/** + * @description listen + * @example + * rflink.listen(); + */ +function listen() { + this.usb.on('data', (data) => { + this.message(data); + }); +} + +module.exports = { + listen, +}; diff --git a/server/services/rflink/lib/commands/rflink.milight.pair.js b/server/services/rflink/lib/commands/rflink.milight.pair.js index 633eaac350..5a716817b1 100644 --- a/server/services/rflink/lib/commands/rflink.milight.pair.js +++ b/server/services/rflink/lib/commands/rflink.milight.pair.js @@ -1,120 +1,101 @@ -const { - DEVICE_FEATURE_CATEGORIES, - DEVICE_FEATURE_TYPES, - } = require('../../../../utils/constants'); - const logger = require('../../../../utils/logger'); - -/** - * @description pair a milight device - * @param {string} currentMilightGateway - Milight gateway. - * @param {string} milightZone - Milight zone. - * @example - * rflink.pair() - */ -function pair(currentMilightGateway, milightZone) { - let newLight; - const number = milightZone; - if (currentMilightGateway !== undefined) { - - - // if (this.currentMilightGateway.number < 10) { - // number = `0${this.currentMilightGateway.number}`; - // } else { - // number = `${this.currentMilightGateway.number}`; - // } - const msg = `10;MiLightv1;${this.currentMilightGateway};0${number};34BC;PAIR;`; - this.sendUsb.write(msg, error => { - }); - this.sendUsb.write(msg, error => { - }); - this.sendUsb.write(msg, error => { - }); - - - newLight = { - service_id : this.serviceId, - name : ` Milight ${currentMilightGateway} number${number} `, - selector : `rflink:milight:${currentMilightGateway}:${number}`, - external_id: `rflink:milight:${currentMilightGateway}:${number}`, - model : `Milight`, - should_poll : false, - features : [ - { - name : 'power', - selector : `rflink:milight:${currentMilightGateway}:${number}:power`, - external_id : `rflink:milight:${currentMilightGateway}:${number}:power`, - rfcode : { - value : 'CMD', - cmd : 'ON', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.BINARY, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 1, - - - }, - { - name : 'color', - selector : `rflink:milight:${currentMilightGateway}:${number}:color`, - external_id : `rflink:milight:${currentMilightGateway}:${number}:color`, - rfcode : { - value : 'RGBW', - cmd : 'COLOR', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 255, - - - }, - { - name : 'brightness', - selector : `rflink:milight:${currentMilightGateway}:${number}:brightness`, - external_id : `rflink:milight:${currentMilightGateway}:${number}:brightness`, - rfcode : { - value : 'RGBW', - cmd : 'BRIGHT', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }, - { - name : 'milight-mode', - selector : `rflink:milight:${currentMilightGateway}:${number}:milight-mode`, - external_id : `rflink:milight:${currentMilightGateway}:${number}:milight-mode`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.MODE, - read_only : false, - keep_history: true, - has_feedback: false, - min: 1, - max: 8, - - - } - - ] - }; - this.addDevice(newLight); - }; - } - -module.exports = { - pair, -}; \ No newline at end of file +const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants'); + +/** + * @description pair a milight device + * @param {string} currentMilightGateway - Milight gateway. + * @param {string} milightZone - Milight zone. + * @example + * rflink.pair() + */ +function pair(currentMilightGateway, milightZone) { + let newLight; + const number = milightZone; + if (currentMilightGateway !== undefined) { + // if (this.currentMilightGateway.number < 10) { + // number = `0${this.currentMilightGateway.number}`; + // } else { + // number = `${this.currentMilightGateway.number}`; + // } + const msg = `10;MiLightv1;${this.currentMilightGateway};0${number};34BC;PAIR;`; + this.sendUsb.write(msg, (error) => {}); + this.sendUsb.write(msg, (error) => {}); + this.sendUsb.write(msg, (error) => {}); + + newLight = { + service_id: this.serviceId, + name: ` Milight ${currentMilightGateway} number${number} `, + selector: `rflink:milight:${currentMilightGateway}:${number}`, + external_id: `rflink:milight:${currentMilightGateway}:${number}`, + model: `Milight`, + should_poll: false, + features: [ + { + name: 'power', + selector: `rflink:milight:${currentMilightGateway}:${number}:power`, + external_id: `rflink:milight:${currentMilightGateway}:${number}:power`, + rfcode: { + value: 'CMD', + cmd: 'ON', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }, + { + name: 'color', + selector: `rflink:milight:${currentMilightGateway}:${number}:color`, + external_id: `rflink:milight:${currentMilightGateway}:${number}:color`, + rfcode: { + value: 'RGBW', + cmd: 'COLOR', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.COLOR, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 255, + }, + { + name: 'brightness', + selector: `rflink:milight:${currentMilightGateway}:${number}:brightness`, + external_id: `rflink:milight:${currentMilightGateway}:${number}:brightness`, + rfcode: { + value: 'RGBW', + cmd: 'BRIGHT', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + }, + { + name: 'milight-mode', + selector: `rflink:milight:${currentMilightGateway}:${number}:milight-mode`, + external_id: `rflink:milight:${currentMilightGateway}:${number}:milight-mode`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.MODE, + read_only: false, + keep_history: true, + has_feedback: false, + min: 1, + max: 8, + }, + ], + }; + this.addDevice(newLight); + } +} + +module.exports = { + pair, +}; diff --git a/server/services/rflink/lib/commands/rflink.milight.unpair.js b/server/services/rflink/lib/commands/rflink.milight.unpair.js index 354d76b276..045be45111 100644 --- a/server/services/rflink/lib/commands/rflink.milight.unpair.js +++ b/server/services/rflink/lib/commands/rflink.milight.unpair.js @@ -1,34 +1,30 @@ -const logger = require('../../../../utils/logger'); -/** - * @description unpair a milight device - * @param {string} currentMilightGateway - Milight gateway. - * @param {string} milightZone - Milight zone. - * @example - * rflink.unpair() - */ -function unpair(currentMilightGateway, milightZone) { - const number = milightZone; - if (this.currentMilightGateway !== undefined) { - - const msg = `10;MiLightv1;${this.currentMilightGateway};0${number};34BC;UNPAIR;`; - logger.log(msg); - this.sendUsb.write(msg, error => { - logger.log(error); - }); - this.sendUsb.write(msg, error => { - logger.log(error); - }); - this.sendUsb.write(msg, error => { - logger.log(error); - }); - } // else { - // show a message in setup tab to tell user that gatewa y is undefined - // } - -} - - - -module.exports = { - unpair, -}; \ No newline at end of file +const logger = require('../../../../utils/logger'); +/** + * @description unpair a milight device + * @param {string} currentMilightGateway - Milight gateway. + * @param {string} milightZone - Milight zone. + * @example + * rflink.unpair() + */ +function unpair(currentMilightGateway, milightZone) { + const number = milightZone; + if (this.currentMilightGateway !== undefined) { + const msg = `10;MiLightv1;${this.currentMilightGateway};0${number};34BC;UNPAIR;`; + logger.log(msg); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); + } // else { + // show a message in setup tab to tell user that gatewa y is undefined + // } +} + +module.exports = { + unpair, +}; diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js index d1e1c56474..82e6e0b956 100644 --- a/server/services/rflink/lib/commands/rflink.setValue.js +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -5,78 +5,62 @@ const logger = require('../../../../utils/logger'); * @param {Object} device - The device to control. * @param {Object} deviceFeature - The name of feature to control. * @param {any} state - The new state. - * @example + * @example * rflink.SetValue(); */ -function setValue(device, deviceFeature, state) { - logger.log(deviceFeature); - let msg; - let value; - logger.log(device.external_id); - - value = state; - - if (deviceFeature.type === 'binary') { - switch (state) { - case 0: - case false : - value = 'OFF'; - break; - case 1: - case true : - value = 'ON'; - - break; - default : - value = state; - break; - - } - } - - +function setValue(device, deviceFeature, state) { + logger.log(deviceFeature); + let msg; + let value; + logger.log(device.external_id); + value = state; + if (deviceFeature.type === 'binary') { + switch (state) { + case 0: + case false: + value = 'OFF'; + break; + case 1: + case true: + value = 'ON'; - if (device.external_id.split(':')[1] === 'milight') { - const id = device.external_id.split(':')[2]; - const channel = `0${device.external_id.split(':')[3]}`; - if (deviceFeature.external_id.split()[2].toLowerCase() === 'color') { - msg = `10;MiLightv1;${id};${channel};${value};COLOR;`; - } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'brightness') { - msg = `10;MiLightv1;${id};${channel};${value};BRIGHT;`; - } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'power') { - msg = `10;MiLightv1;${id};${channel};34BC;${value};`; - } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'milight-mode') { - msg = `10;MiLightv1;${id};${channel};34BC;MODE${value};`; - } - - - - - - - } else { - - msg = ObjToRF(device, deviceFeature, value); + break; + default: + value = state; + break; + } + } + if (device.external_id.split(':')[1] === 'milight') { + const id = device.external_id.split(':')[2]; + const channel = `0${device.external_id.split(':')[3]}`; + if (deviceFeature.external_id.split()[2].toLowerCase() === 'color') { + msg = `10;MiLightv1;${id};${channel};${value};COLOR;`; + } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'brightness') { + msg = `10;MiLightv1;${id};${channel};${value};BRIGHT;`; + } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'power') { + msg = `10;MiLightv1;${id};${channel};34BC;${value};`; + } else if (deviceFeature.external_id.split()[2].toLowerCase() === 'milight-mode') { + msg = `10;MiLightv1;${id};${channel};34BC;MODE${value};`; } - logger.log(msg); - - this.sendUsb.write(msg, error => { - logger.log(error); - }); - this.sendUsb.write(msg, error => { - logger.log(error); - }); - this.sendUsb.write(msg, error => { - logger.log(error); - }); - + } else { + msg = ObjToRF(device, deviceFeature, value); + } + logger.log(msg); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); + this.sendUsb.write(msg, (error) => { + logger.log(error); + }); } - module.exports = { - setValue, -}; \ No newline at end of file + setValue, +}; diff --git a/server/services/rflink/lib/events/rflink.addDevice.js b/server/services/rflink/lib/events/rflink.addDevice.js index e8b29e75f6..0f82ed78f9 100644 --- a/server/services/rflink/lib/events/rflink.addDevice.js +++ b/server/services/rflink/lib/events/rflink.addDevice.js @@ -1,38 +1,24 @@ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable no-restricted-syntax */ -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - - -/** - * @description Add device. - * @param {Object} device - Device to add. - * @example - * Rflink.addDevice(device); - */ -function addDevice(device) { - const id = device.external_id.split(':')[1]; - - - this.gladys.event.emit(EVENTS.DEVICE.NEW, - device, - ); - - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, - payload : device, - }); - - this.device[id] = device; - - - - - - - - - - -} - -module.exports = {addDevice}; \ No newline at end of file +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-restricted-syntax */ +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description Add device. + * @param {Object} device - Device to add. + * @example + * Rflink.addDevice(device); + */ +function addDevice(device) { + const id = device.external_id.split(':')[1]; + + this.gladys.event.emit(EVENTS.DEVICE.NEW, device); + + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, + payload: device, + }); + + this.device[id] = device; +} + +module.exports = { addDevice }; diff --git a/server/services/rflink/lib/events/rflink.connected.js b/server/services/rflink/lib/events/rflink.connected.js index 277069cc16..b06124bc50 100644 --- a/server/services/rflink/lib/events/rflink.connected.js +++ b/server/services/rflink/lib/events/rflink.connected.js @@ -1,21 +1,21 @@ -const logger = require('../../../../utils/logger'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - -/** - * @description When the gateway is connected - * @example - * rflink.on('connected', this.connected); - */ -function connected() { - logger.debug(`Rflink : Gateway is connected`); - this.connected = true; - this.ready = true; - this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, - payload: {}, - }); -} - -module.exports = { - connected, -}; +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When the gateway is connected + * @example + * rflink.on('connected', this.connected); + */ +function connected() { + logger.debug(`Rflink : Gateway is connected`); + this.connected = true; + this.ready = true; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, + payload: {}, + }); +} + +module.exports = { + connected, +}; diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 6dce9b555c..25ab122253 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -1,407 +1,358 @@ -const logger = require('../../../../utils/logger'); -const RFtoObj = require('../../api/rflink.parse.RFtoObject'); -const { - DEVICE_FEATURE_CATEGORIES, - DEVICE_FEATURE_TYPES, - DEVICE_FEATURE_UNITS, - } = require('../../../../utils/constants'); - - - -/** - * @description when a message is received by the rflink gateway - * @param {string} msgRF - The message. - * @example - * rflink.message(msg); - */ -function message(msgRF) { - console.log(msgRF); - this.lastCommand = msgRF; - const msg = RFtoObj(msgRF); - let newDevice; - - if (typeof msg.id === 'string'){ - if (msg.id.includes('=') === false) { - - const doesntExistYet = this.device[msg.id] === undefined; - - if (doesntExistYet === true) { - - - - - - const model = `${msg.protocol.charAt(0).toUpperCase()}${msg.protocol.toLowerCase().slice(1)}`; - - console.log(model); - newDevice = { - service_id : this.serviceId, - name : `${msg.protocol} `, - selector : `rflink:${msg.id}:${msg.switch}`, - external_id: `rflink:${msg.id}:${msg.switch}`, - model, - should_poll : false, - features : [] - }; - - - if (msg.temp !== undefined) { - newDevice.name += 'temperature sensor'; - newDevice.features.push({ - name : 'temperature', - selector : `rflink:${msg.id}:temperature:${msg.switch}`, - external_id : `rflink:${msg.id}:temperature:${msg.switch}`, - rfcode : 'TEMP', - category : DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.CELSIUS, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.hum !== undefined) { - newDevice.features.push({ - name : 'humidity', - selector : `rflink:${msg.id}:humidity:${msg.switch}`, - external_id : `rflink:${msg.id}:humidity:${msg.switch}`, - rfcode : 'HUM', - category : DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PERCENT, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.baro !== undefined) { - newDevice.name += 'pressure sensor'; - newDevice.features.push({ - name : 'pressure', - selector : `rflink:${msg.id}:pressure:${msg.switch}`, - external_id : `rflink:${msg.id}:pressure:${msg.switch}`, - rfcode : 'BARO', - category : DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PASCAL, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100000000, - - - }); - } - if (msg.uv !== undefined) { - newDevice.name += 'uv sensor'; - newDevice.features.push({ - name : 'uv intensity', - selector : `rflink:${msg.id}:uv:${msg.switch}`, - external_id : `rflink:${msg.id}:uv:${msg.switch}`, - rfcode : 'UV', - category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.lux !== undefined) { - newDevice.name += 'light sensor'; - newDevice.features.push({ - name : 'light intensity', - selector : `rflink:${msg.id}:light-intensity:${msg.switch}`, - external_id : `rflink:${msg.id}:light-intensity:${msg.switch}`, - rfcode : 'LUX', - category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.LUX, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.bat !== undefined) { - newDevice.features.push({ - name : 'battery', - selector : `rflink:${msg.id}:battery:${msg.switch}`, - external_id : `rflink:${msg.id}:battery:${msg.switch}`, - rfcode : 'BAT', - category : DEVICE_FEATURE_CATEGORIES.BATTERY, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PERCENT, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.rain !== undefined || msg.rainrate !== undefined) { - newDevice.name += 'rain sensor'; - newDevice.features.push({ - name : 'rain', - selector : `rflink:${msg.id}:rain:${msg.switch}`, - external_id : `rflink:${msg.id}:rain:${msg.switch}`, - rfcode : 'RAIN', - category : DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 1000, - - - }); - } - if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { - newDevice.name += 'wind speed sensor'; - newDevice.features.push({ - name : 'wind speed', - selector : `rflink:${msg.id}:wind-speed:${msg.switch}`, - external_id : `rflink:${msg.id}:wind-speed:${msg.switch}`, - rfcode : 'WINSP', - category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 500, - - - }); - } - if (msg.windir !== undefined) { - newDevice.name += 'wind direction sensor'; - newDevice.features.push({ - name : 'wind direction', - selector : `rflink:${msg.id}:wind-dir:${msg.switch}`, - external_id : `rflink:${msg.id}:wind-dir:${msg.switch}`, - rfcode : 'WINDIR', - category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.co2 !== undefined) { - newDevice.name += 'co2 sensor'; - newDevice.features.push({ - name : 'co2', - selector : `rflink:${msg.id}:co2:${msg.switch}`, - external_id : `rflink:${msg.id}:co2:${msg.switch}`, - rfcode : 'CO2', - category : DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 1000, - - - }); - } - if (msg.switch !== undefined && msg.rgwb === undefined &&(msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF')) { - newDevice.name += 'switch'; - newDevice.features.push({ - name : 'switch', - selector : `rflink:${msg.id}:switch:${msg.switch}`, - external_id : `rflink:${msg.id}:switch:${msg.switch}`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.SWITCH, - type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 1, - - - }); - } - - - if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { - newDevice.selector = `rflink:milight:${msg.id}:${msg.switch}`; - newDevice.external_id = `rflink:milight:${msg.id}:${msg.switch}`; - newDevice.features.push( - { - name : 'Power', - selector : `rflink:milight:${msg.id}:${msg.switch}:power`, - external_id : `rflink:milight:${msg.id}:${msg.switch}:power`, - rfcode : { - value : 'CMD', - cmd : 'ON', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.BINARY, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 1, - - - }, - { - name : 'color', - selector : `rflink:milight:${msg.id}:${msg.switch}:color`, - external_id : `rflink:milight:${msg.id}:${msg.switch}:color`, - rfcode : { - value : 'RGBW', - cmd : 'COLOR', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 255, - - - }); - newDevice.features.push({ - name : 'brightness', - selector : `rflink:milight:${msg.id}:${msg.switch}:brightness`, - external_id : `rflink:milight:${msg.id}:${msg.switch}:brightness`, - rfcode : { - value : 'RGBW', - cmd : 'BRIGHT', - }, - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - newDevice.features.push({ - name : 'milight-mode', - selector : `rflink:milight:${msg.id}:${msg.switch}:milight-mode`, - external_id : `rflink:milight:${msg.id}:${msg.switch}:milight-mode`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.MODE, - read_only : false, - keep_history: true, - has_feedback: false, - min: 1, - max: 8, - - - }); - - - } - - - this.addDevice(newDevice); - - } else if (doesntExistYet === false) { - - if (msg.temp !== undefined) { - this.newValue(msg, 'temperature', msg.temp); - } - if (msg.hum !== undefined) { - this.newValue(msg, 'humidity', msg.hum); - } - if (msg.uv !== undefined) { - this.newValue(msg, 'uv', msg.uv); - } - if (msg.lux !== undefined) { - this.newValue(msg, 'light-intensity', msg.lux); - } - if (msg.bat !== undefined) { - this.newValue(msg, 'battery', msg.bat); - } - if (msg.rain !== undefined) { - this.newValue(msg, 'rain', msg.rain); - } - if (msg.temp !== undefined) { - this.newValue(msg, 'temperature', msg.temp); - } - if (msg.winsp !== undefined) { - this.newValue(msg, 'wind-speed', msg.winsp); - } - if (msg.awinsp !== undefined) { - this.newValue(msg, 'wind-speed', msg.awinsp); - } - if (msg.wings !== undefined) { - this.newValue(msg, 'wind-speed', msg.wings); - } - if (msg.windir !== undefined) { - this.newValue(msg, 'wind-dir', msg.windir); - } - if (msg.co2 !== undefined) { - this.newValue(msg, 'co2', msg.co2); - } - if (msg.wings !== undefined) { - this.newValue(msg, 'wind-speed', msg.wings); - } - if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { - this.newValue(msg, 'switch', msg.cmd); - } - if (msg.rgbw !== undefined) { - this.newValue(msg, 'color', msg.rgbw); - this.newValue(msg, 'brightness', msg.rgbw); - } - if (msg.cmd.includes('MODE') === true ) { - this.newValue(msg, 'milight-mode', msg.cmd); - } - if (msg.cmd.includes('DISCO') === true) { - this.newValue(msg, 'milight-mode', msg.cmd); - } - - - - - - - } - // const features = - - }else { - logger.log(`${msg.id} n'est pas une id valide`); - } - - } - - - -} - -module.exports = { - message, -}; - +const logger = require('../../../../utils/logger'); +const RFtoObj = require('../../api/rflink.parse.RFtoObject'); +const { + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES, + DEVICE_FEATURE_UNITS, +} = require('../../../../utils/constants'); + +/** + * @description when a message is received by the rflink gateway + * @param {string} msgRF - The message. + * @example + * rflink.message(msg); + */ +function message(msgRF) { + this.lastCommand = msgRF; + const msg = RFtoObj(msgRF); + let newDevice; + + if (typeof msg.id === 'string') { + if (msg.id.includes('=') === false) { + const doesntExistYet = this.device[msg.id] === undefined; + + if (doesntExistYet === true) { + const model = `${msg.protocol.charAt(0).toUpperCase()}${msg.protocol.toLowerCase().slice(1)}`; + + newDevice = { + service_id: this.serviceId, + name: `${msg.protocol} `, + selector: `rflink:${msg.id}:${msg.switch}`, + external_id: `rflink:${msg.id}:${msg.switch}`, + model, + should_poll: false, + features: [], + }; + + if (msg.temp !== undefined) { + newDevice.name += 'temperature sensor'; + newDevice.features.push({ + name: 'temperature', + selector: `rflink:${msg.id}:temperature:${msg.switch}`, + external_id: `rflink:${msg.id}:temperature:${msg.switch}`, + rfcode: 'TEMP', + category: DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit: DEVICE_FEATURE_UNITS.CELSIUS, + read_only: true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + }); + } + if (msg.hum !== undefined) { + newDevice.features.push({ + name: 'humidity', + selector: `rflink:${msg.id}:humidity:${msg.switch}`, + external_id: `rflink:${msg.id}:humidity:${msg.switch}`, + rfcode: 'HUM', + category: DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit: DEVICE_FEATURE_UNITS.PERCENT, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + }); + } + if (msg.baro !== undefined) { + newDevice.name += 'pressure sensor'; + newDevice.features.push({ + name: 'pressure', + selector: `rflink:${msg.id}:pressure:${msg.switch}`, + external_id: `rflink:${msg.id}:pressure:${msg.switch}`, + rfcode: 'BARO', + category: DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit: DEVICE_FEATURE_UNITS.PASCAL, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100000000, + }); + } + if (msg.uv !== undefined) { + newDevice.name += 'uv sensor'; + newDevice.features.push({ + name: 'uv intensity', + selector: `rflink:${msg.id}:uv:${msg.switch}`, + external_id: `rflink:${msg.id}:uv:${msg.switch}`, + rfcode: 'UV', + category: DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only: true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + }); + } + if (msg.lux !== undefined) { + newDevice.name += 'light sensor'; + newDevice.features.push({ + name: 'light intensity', + selector: `rflink:${msg.id}:light-intensity:${msg.switch}`, + external_id: `rflink:${msg.id}:light-intensity:${msg.switch}`, + rfcode: 'LUX', + category: DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit: DEVICE_FEATURE_UNITS.LUX, + read_only: true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + }); + } + if (msg.bat !== undefined) { + newDevice.features.push({ + name: 'battery', + selector: `rflink:${msg.id}:battery:${msg.switch}`, + external_id: `rflink:${msg.id}:battery:${msg.switch}`, + rfcode: 'BAT', + category: DEVICE_FEATURE_CATEGORIES.BATTERY, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit: DEVICE_FEATURE_UNITS.PERCENT, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + }); + } + if (msg.rain !== undefined || msg.rainrate !== undefined) { + newDevice.name += 'rain sensor'; + newDevice.features.push({ + name: 'rain', + selector: `rflink:${msg.id}:rain:${msg.switch}`, + external_id: `rflink:${msg.id}:rain:${msg.switch}`, + rfcode: 'RAIN', + category: DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + }); + } + if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { + newDevice.name += 'wind speed sensor'; + newDevice.features.push({ + name: 'wind speed', + selector: `rflink:${msg.id}:wind-speed:${msg.switch}`, + external_id: `rflink:${msg.id}:wind-speed:${msg.switch}`, + rfcode: 'WINSP', + category: DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 500, + }); + } + if (msg.windir !== undefined) { + newDevice.name += 'wind direction sensor'; + newDevice.features.push({ + name: 'wind direction', + selector: `rflink:${msg.id}:wind-dir:${msg.switch}`, + external_id: `rflink:${msg.id}:wind-dir:${msg.switch}`, + rfcode: 'WINDIR', + category: DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + }); + } + if (msg.co2 !== undefined) { + newDevice.name += 'co2 sensor'; + newDevice.features.push({ + name: 'co2', + selector: `rflink:${msg.id}:co2:${msg.switch}`, + external_id: `rflink:${msg.id}:co2:${msg.switch}`, + rfcode: 'CO2', + category: DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, + type: DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only: true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + }); + } + if ( + msg.switch !== undefined && + msg.rgwb === undefined && + (msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') + ) { + newDevice.name += 'switch'; + newDevice.features.push({ + name: 'switch', + selector: `rflink:${msg.id}:switch:${msg.switch}`, + external_id: `rflink:${msg.id}:switch:${msg.switch}`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.SWITCH, + type: DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }); + } + + if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { + newDevice.selector = `rflink:milight:${msg.id}:${msg.switch}`; + newDevice.external_id = `rflink:milight:${msg.id}:${msg.switch}`; + newDevice.features.push( + { + name: 'Power', + selector: `rflink:milight:${msg.id}:${msg.switch}:power`, + external_id: `rflink:milight:${msg.id}:${msg.switch}:power`, + rfcode: { + value: 'CMD', + cmd: 'ON', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }, + { + name: 'color', + selector: `rflink:milight:${msg.id}:${msg.switch}:color`, + external_id: `rflink:milight:${msg.id}:${msg.switch}:color`, + rfcode: { + value: 'RGBW', + cmd: 'COLOR', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.COLOR, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 255, + }, + ); + newDevice.features.push({ + name: 'brightness', + selector: `rflink:milight:${msg.id}:${msg.switch}:brightness`, + external_id: `rflink:milight:${msg.id}:${msg.switch}:brightness`, + rfcode: { + value: 'RGBW', + cmd: 'BRIGHT', + }, + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + }); + newDevice.features.push({ + name: 'milight-mode', + selector: `rflink:milight:${msg.id}:${msg.switch}:milight-mode`, + external_id: `rflink:milight:${msg.id}:${msg.switch}:milight-mode`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.LIGHT, + type: DEVICE_FEATURE_TYPES.LIGHT.MODE, + read_only: false, + keep_history: true, + has_feedback: false, + min: 1, + max: 8, + }); + } + + this.addDevice(newDevice); + } else if (doesntExistYet === false) { + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.hum !== undefined) { + this.newValue(msg, 'humidity', msg.hum); + } + if (msg.uv !== undefined) { + this.newValue(msg, 'uv', msg.uv); + } + if (msg.lux !== undefined) { + this.newValue(msg, 'light-intensity', msg.lux); + } + if (msg.bat !== undefined) { + this.newValue(msg, 'battery', msg.bat); + } + if (msg.rain !== undefined) { + this.newValue(msg, 'rain', msg.rain); + } + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.winsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.winsp); + } + if (msg.awinsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.awinsp); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.windir !== undefined) { + this.newValue(msg, 'wind-dir', msg.windir); + } + if (msg.co2 !== undefined) { + this.newValue(msg, 'co2', msg.co2); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if ( + (msg.switch !== undefined && msg.cmd === 'ON') || + msg.cmd === 'OFF' || + msg.cmd === 'ALLON' || + msg.cmd === 'ALLOFF' + ) { + this.newValue(msg, 'switch', msg.cmd); + } + if (msg.rgbw !== undefined) { + this.newValue(msg, 'color', msg.rgbw); + this.newValue(msg, 'brightness', msg.rgbw); + } + if (msg.cmd.includes('MODE') === true) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + if (msg.cmd.includes('DISCO') === true) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + } + // const features = + } else { + logger.log(`${msg.id} n'est pas une id valide`); + } + } +} + +module.exports = { + message, +}; diff --git a/server/services/rflink/lib/events/rflink.newValue.js b/server/services/rflink/lib/events/rflink.newValue.js index fefdad52b8..ab69dfdfce 100644 --- a/server/services/rflink/lib/events/rflink.newValue.js +++ b/server/services/rflink/lib/events/rflink.newValue.js @@ -9,16 +9,14 @@ const { EVENTS } = require('../../../../utils/constants'); * newValue(Object, 'temperature', 30) */ function newValue(device, deviceFeature, state) { + logger.debug(`RFlink : value ${deviceFeature} of device ${device} changed to ${state}`); - logger.debug(`RFlink : value ${deviceFeature} of device ${device} changed to ${state}`); - - this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { - device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, - state, - }); - + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, + state, + }); } module.exports = { - newValue, -}; \ No newline at end of file + newValue, +}; diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index bddf6f4612..32dbd89754 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -1,65 +1,44 @@ - -// Events - -const { newValue } = require('./events/rflink.newValue'); -const { addDevice } = require('./events/rflink.addDevice'); -const { message } = require('./events/rflink.message.js'); - - -// COMMANDS -const { setValue } = require('./commands/rflink.setValue'); -const { connect } = require('./commands/rflink.connect'); -const { disconnect } = require('./commands/rflink.disconnect'); -const { listen } = require('./commands/rflink.listen'); -const { getDevices } = require('./commands/rflink.getDevices'); -const { pair } = require('./commands/rflink.milight.pair'); -const { unpair } = require('./commands/rflink.milight.unpair'); - - - - - -const RFlinkManager = function RFlinkManager(gladys, serviceId) { - this.gladys = gladys; - this.scanInProgress = false; - this.serviceId = serviceId; - this.connected = false; - this.ready = false; - this.scanInProgress = false; - this.device = {}; - this.currentMilightGateway = 'F746'; - this.milightBridges = {}; - - -}; - -// Events - -RFlinkManager.prototype.message = message; -RFlinkManager.prototype.newValue = newValue; -RFlinkManager.prototype.addDevice = addDevice; - - -// Commands - -RFlinkManager.prototype.setValue = setValue; -RFlinkManager.prototype.connect = connect; -RFlinkManager.prototype.disconnect = disconnect; -RFlinkManager.prototype.listen = listen; -RFlinkManager.prototype.getDevices = getDevices; -RFlinkManager.prototype.pair = pair; -RFlinkManager.prototype.unpair = unpair; - - - - - - - - - - - - - -module.exports = RFlinkManager; \ No newline at end of file +// Events + +const { newValue } = require('./events/rflink.newValue'); +const { addDevice } = require('./events/rflink.addDevice'); +const { message } = require('./events/rflink.message.js'); + +// COMMANDS +const { setValue } = require('./commands/rflink.setValue'); +const { connect } = require('./commands/rflink.connect'); +const { disconnect } = require('./commands/rflink.disconnect'); +const { listen } = require('./commands/rflink.listen'); +const { getDevices } = require('./commands/rflink.getDevices'); +const { pair } = require('./commands/rflink.milight.pair'); +const { unpair } = require('./commands/rflink.milight.unpair'); + +const RFlinkManager = function RFlinkManager(gladys, serviceId) { + this.gladys = gladys; + this.scanInProgress = false; + this.serviceId = serviceId; + this.connected = false; + this.ready = false; + this.scanInProgress = false; + this.device = {}; + this.currentMilightGateway = 'F746'; + this.milightBridges = {}; +}; + +// Events + +RFlinkManager.prototype.message = message; +RFlinkManager.prototype.newValue = newValue; +RFlinkManager.prototype.addDevice = addDevice; + +// Commands + +RFlinkManager.prototype.setValue = setValue; +RFlinkManager.prototype.connect = connect; +RFlinkManager.prototype.disconnect = disconnect; +RFlinkManager.prototype.listen = listen; +RFlinkManager.prototype.getDevices = getDevices; +RFlinkManager.prototype.pair = pair; +RFlinkManager.prototype.unpair = unpair; + +module.exports = RFlinkManager; diff --git a/server/services/rflink/package.json b/server/services/rflink/package.json index 00c4a44fd9..35d558c142 100644 --- a/server/services/rflink/package.json +++ b/server/services/rflink/package.json @@ -1,22 +1,21 @@ -{ - "author": { - "name": "mathis tondenier" - }, - "name": "gladys-rflink", - "main": "index.js", - "os": [ - "darwin", - "linux", - "win32" - ], - "cpu": [ - "x64", - "arm", - "arm64" - ], - "dependencies": { - "serialport": "8.0.7", - "@serialport/parser-readline": "8.0.7" - - } -} +{ + "author": { + "name": "mathis tondenier" + }, + "name": "gladys-rflink", + "main": "index.js", + "os": [ + "darwin", + "linux", + "win32" + ], + "cpu": [ + "x64", + "arm", + "arm64" + ], + "dependencies": { + "serialport": "8.0.7", + "@serialport/parser-readline": "8.0.7" + } +} From 5fecc26fd560fa431c4487ef0b2fff5172b5f1ab Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:25:59 +0100 Subject: [PATCH 043/236] prettier --- .../all/rflink/device-page/actions.js | 9 +++---- .../all/rflink/device-page/index.js | 3 --- .../all/rflink/device-page/setup/index.js | 21 +++++---------- .../all/rflink/settings-page/actions.js | 26 ++++++------------- 4 files changed, 18 insertions(+), 41 deletions(-) diff --git a/front/src/routes/integration/all/rflink/device-page/actions.js b/front/src/routes/integration/all/rflink/device-page/actions.js index ba81299f90..a4691b45f4 100644 --- a/front/src/routes/integration/all/rflink/device-page/actions.js +++ b/front/src/routes/integration/all/rflink/device-page/actions.js @@ -6,7 +6,7 @@ import createActionsIntegration from '../../../../../actions/integration'; function createActions(store) { const integrationActions = createActionsIntegration(store); - store.setState({integrationActions}); + store.setState({ integrationActions }); const houseActions = createActionsHouse(store); const actions = { async getRflinkDevices(state, take, skip) { @@ -33,14 +33,11 @@ function createActions(store) { }); } - - console.log(rflinkDevices); store.setState({ rflinkDevices, getRflinkDevicesStatus: RequestStatus.Success }); } catch (e) { - console.log(e); store.setState({ getRflinkDevicesStatus: RequestStatus.Error }); @@ -64,7 +61,7 @@ function createActions(store) { async deleteDevice(state, device, index) { await state.httpClient.delete('/api/v1/device/' + device.selector); await state.httpClient.post('/api/v1/service/rflink/remove/', { - external_id : device.selector, + external_id: device.selector }); const newState = update(state, { rflinkDevices: { @@ -87,7 +84,7 @@ function createActions(store) { } }; actions.debouncedSearch = debounce(actions.search, 200); - return Object.assign({}, houseActions, integrationActions ,actions); + return Object.assign({}, houseActions, integrationActions, actions); } export default createActions; diff --git a/front/src/routes/integration/all/rflink/device-page/index.js b/front/src/routes/integration/all/rflink/device-page/index.js index 2e1dc43175..aae4b828bc 100644 --- a/front/src/routes/integration/all/rflink/device-page/index.js +++ b/front/src/routes/integration/all/rflink/device-page/index.js @@ -12,8 +12,6 @@ class RflinkDevicePage extends Component { this.props.getHouses(); } - - render(props, {}) { return ( @@ -22,6 +20,5 @@ class RflinkDevicePage extends Component { ); } } -console.log(this.props); export default RflinkDevicePage; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/index.js b/front/src/routes/integration/all/rflink/device-page/setup/index.js index 0b47f9cc98..485d959c64 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/index.js +++ b/front/src/routes/integration/all/rflink/device-page/setup/index.js @@ -9,7 +9,6 @@ import get from 'get-value'; import update from 'immutability-helper'; import { RequestStatus } from '../../../../../../utils/consts'; - @connect('session,user,httpClient,houses,currentIntegration', actions) class RflinkDeviceSetupPage extends Component { selectFeature(e) { @@ -20,10 +19,8 @@ class RflinkDeviceSetupPage extends Component { addFeature() { const featureData = this.state.selectedFeature.split('|'); - const device = update(this.state.device, { - features: { $push: [ { @@ -98,10 +95,8 @@ class RflinkDeviceSetupPage extends Component { loading: true }); try { - console.log(this.state.device.features[0].external_id); //// Mauvais chemin ? const id = this.state.device.features[0].external_id.split(':'); this.state.device.external_id = `rflink:${id[1]}:${id[3]}`; - console.log(this.state.device); const device = await this.props.httpClient.post('/api/v1/device', this.state.device); this.setState({ saveStatus: RequestStatus.Success, @@ -165,19 +160,17 @@ class RflinkDeviceSetupPage extends Component { loadedDevice.service_id === this.props.currentIntegration.id ) { device = loadedDevice; - } } - for (let feature in device.features) { - if (feature !== undefined) { - if (device.external_id.split(':')[1] !== 'milight') { - device.features[`${feature}`].switchId = device.features[`${feature}`].external_id.split(':')[1]; - device.features[`${feature}`].switchNumber = device.features[`${feature}`].external_id.split(':')[3]; - } - } + for (let feature in device.features) { + if (feature !== undefined) { + if (device.external_id.split(':')[1] !== 'milight') { + device.features[`${feature}`].switchId = device.features[`${feature}`].external_id.split(':')[1]; + device.features[`${feature}`].switchNumber = device.features[`${feature}`].external_id.split(':')[3]; } - console.log(device); + } + } this.setState({ device, loading: false diff --git a/front/src/routes/integration/all/rflink/settings-page/actions.js b/front/src/routes/integration/all/rflink/settings-page/actions.js index 6357fc054e..a3d406b4f9 100644 --- a/front/src/routes/integration/all/rflink/settings-page/actions.js +++ b/front/src/routes/integration/all/rflink/settings-page/actions.js @@ -40,7 +40,7 @@ const actions = store => { }); }, updateDebugCommand(state, e) { - store.setState({commandToSend : e.target.value}); + store.setState({ commandToSend: e.target.value }); }, updateMilight(state, e) { store.setState({ @@ -94,13 +94,9 @@ const actions = store => { value: state.currentMilightGateway }); await state.httpClient.post('/api/v1/service/rflink/pair', { - zone : state.currentMilightZone, + zone: state.currentMilightZone }); - - - } catch (e) { - - } + } catch (e) {} }, async unpair(state) { try { @@ -108,22 +104,16 @@ const actions = store => { value: state.currentMilightGateway }); await state.httpClient.post('/api/v1/service/rflink/unpair', { - zone : state.currentMilightZone, + zone: state.currentMilightZone }); - - } catch (e) { - - } - + } catch (e) {} }, async sendDebug(state) { - try{ + try { await state.httpClient.post('/api/v1/service/rflink/debug', { value: state.commandToSend }); - } catch (e) { - - } + } catch (e) {} }, async getStatus(state) { store.setState({ @@ -135,7 +125,7 @@ const actions = store => { if (currentMilightGateway === undefined || currentMilightGateway === null) { currentMilightGateway = 'error'; } - + store.setState({ rflinkStatus, currentMilightGateway, From 8e5b8841959509d87baee78b2966a73251905b08 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:27:21 +0100 Subject: [PATCH 044/236] prettier --- front/src/config/demo.json | 22 +++++++++++++++ front/src/config/i18n/en.json | 29 +++++++++----------- front/src/config/integrations/device.en.json | 2 +- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/front/src/config/demo.json b/front/src/config/demo.json index 13f9bd67c5..59bf6fe1c3 100644 --- a/front/src/config/demo.json +++ b/front/src/config/demo.json @@ -626,6 +626,28 @@ ] } ], + "get /api/v1/service/rflink/device" : [ + { + "id": "fbedb47f-4d25-4381-8923-2633b23192a0", + "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", + "room_id": "cecc52c7-3e67-4b75-9b13-9a8867b0443d", + "name": "PC bureau", + "selector": "rflink:1234", + "external_id": "rflink:86aa6:switch:10", + "should_poll": false, + "poll_frequency": null, + "created_at": "2019-02-12T07:49:07.556Z", + "updated_at": "2019-02-12T07:49:07.556Z", + "features": [ + { + "name": "power", + "selector": "switch-test", + "category": "switch", + "type": "binary" + } + ] + } + ], "get /api/v1/service/zwave/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 5e38b8dad9..31fd7718cf 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -188,27 +188,25 @@ "connectedWithSuccess": "Rflink Gateway connected with success.", "connecting": "Trying to connect to Rflink Gateway ...", "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", - "debug" : { - "title" : "Rflink debug console", - "info" : "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ...", - "placeholder" : "message to send", - "sendButton" : "Send" - - + "debug": { + "title": "Rflink debug console", + "info": "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ...", + "placeholder": "message to send", + "sendButton": "Send" }, - "milight" : { - "title" : "Rflink Milight settings", - "gatewayBarinfo" : "Gateway number: ", - "zoneInfo" : "Gateway zone: ", - "about" : " You can use your actual milight bridge id or if you don't have a gateway , use a new one (F746 by default) the id is just a code to identify the gateway. You can use unlimited milight but each bridge has 4 zones", - "pairButton" : "Pair", - "unpairButton" : "Unpair" + "milight": { + "title": "Rflink Milight settings", + "gatewayBarinfo": "Gateway number: ", + "zoneInfo": "Gateway zone: ", + "about": " You can use your actual milight bridge id or if you don't have a gateway , use a new one (F746 by default) the id is just a code to identify the gateway. You can use unlimited milight but each bridge has 4 zones", + "pairButton": "Pair", + "unpairButton": "Unpair" } }, "feature": { "nameLabel": "Name", "model": "Model", - "message" : "You can only create actuators , sensors are automatically detected and added in the device tab when they send messages", + "message": "You can only create actuators , sensors are automatically detected and added in the device tab when they send messages", "namePlaceholder": "Enter feature name", "switchIdLabel": "ID ", "switchIdMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", @@ -247,7 +245,6 @@ "saveError": "Error saving or deleting device", "saveConflictError": "Conflict: Are you sure all device feature external IDs are unique ?" } - }, "telegram": { "title": "Telegram", diff --git a/front/src/config/integrations/device.en.json b/front/src/config/integrations/device.en.json index 846579f74b..af1c94abb1 100644 --- a/front/src/config/integrations/device.en.json +++ b/front/src/config/integrations/device.en.json @@ -35,7 +35,7 @@ "description": "Control your Sonoff devices.", "img": "/assets/integrations/cover/sonoff.jpg" }, - { + { "key": "rflink", "name": "rflink", "description": "Control your rflink devices.", From 5dadc1b70121497c87aa2b1d0cc31af9e2ac7022 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:28:03 +0100 Subject: [PATCH 045/236] prettier From 2d02e3a20ad2e2c043d6482b2da91e3173822b83 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:32:08 +0100 Subject: [PATCH 046/236] Delete rflink.error.js --- .../rflink/lib/events/rflink.error.js | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 server/services/rflink/lib/events/rflink.error.js diff --git a/server/services/rflink/lib/events/rflink.error.js b/server/services/rflink/lib/events/rflink.error.js deleted file mode 100644 index 45abd98f87..0000000000 --- a/server/services/rflink/lib/events/rflink.error.js +++ /dev/null @@ -1,25 +0,0 @@ -const logger = require('../../../../utils/logger'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - -/** - * @description When an error occur - * @param {string} e - The error. - * @example - * rflink.on('rflink.error', this.driverError); - */ -function error(e) { - - if (e === 'NoState') { - this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.RFLINK.NO_STATE_ERROR, - payload: {}, - }); - } - - logger.debug(`RFlink: can't start rflink service`); - -} - -module.exports = { - error, -}; From a4ce4e7b5572d5e2e7b431c4291ffad9966d8750 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:42:43 +0100 Subject: [PATCH 047/236] bug fixes --- .../integration/all/rflink/RflinkPage.jsx | 96 ++--- .../all/rflink/device-page/Device.jsx | 310 +++++++------- .../all/rflink/device-page/DeviceForm.jsx | 217 +++++----- .../all/rflink/device-page/DevicePage.jsx | 154 +++---- .../all/rflink/device-page/setup/Feature.jsx | 405 +++++++++--------- .../rflink/device-page/setup/FeatureTab.jsx | 220 +++++----- .../all/rflink/settings-page/SettingsTab.jsx | 308 ++++++------- 7 files changed, 831 insertions(+), 879 deletions(-) diff --git a/front/src/routes/integration/all/rflink/RflinkPage.jsx b/front/src/routes/integration/all/rflink/RflinkPage.jsx index 1194294789..d94c18c93a 100644 --- a/front/src/routes/integration/all/rflink/RflinkPage.jsx +++ b/front/src/routes/integration/all/rflink/RflinkPage.jsx @@ -1,48 +1,48 @@ -import { Text } from 'preact-i18n'; -import { Link } from 'preact-router/match'; - -const RflinkPage = ({ children, ...props }) => ( -
-
-
-
-
-
-

- -

-
-
- - - - - - - - - - - - -
-
-
- -
{children}
-
-
-
-
-
-); - -export default RflinkPage; +import { Text } from 'preact-i18n'; +import { Link } from 'preact-router/match'; + +const RflinkPage = ({ children, ...props }) => ( +
+
+
+
+
+
+

+ +

+
+
+ + + + + + + + + + + + +
+
+
+ +
{children}
+
+
+
+
+
+); + +export default RflinkPage; diff --git a/front/src/routes/integration/all/rflink/device-page/Device.jsx b/front/src/routes/integration/all/rflink/device-page/Device.jsx index b374524453..8d48a5aea7 100644 --- a/front/src/routes/integration/all/rflink/device-page/Device.jsx +++ b/front/src/routes/integration/all/rflink/device-page/Device.jsx @@ -1,155 +1,155 @@ -import { Text, Localizer } from 'preact-i18n'; -import { Component } from 'preact'; -import cx from 'classnames'; -import get from 'get-value'; -import { Link } from 'preact-router/match'; -import { DEVICE_FEATURE_CATEGORIES } from '../../../../../../../server/utils/constants'; -import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; - -class RflinkDeviceBox extends Component { - refreshDeviceProperty = () => { - if (!this.props.device.features) { - return null; - } - const batteryLevelDeviceFeature = this.props.device.features.find( - deviceFeature => deviceFeature.category === DEVICE_FEATURE_CATEGORIES.BATTERY - ); - const batteryLevel = get(batteryLevelDeviceFeature, 'last_value'); - this.setState({ - batteryLevel - }); - }; - saveDevice = async () => { - this.setState({ loading: true }); - try { - await this.props.saveDevice(this.props.device); - } catch (e) { - this.setState({ error: RequestStatus.Error }); - } - this.setState({ loading: false }); - }; - deleteDevice = async () => { - this.setState({ loading: true }); - try { - await this.props.deleteDevice(this.props.device, this.props.deviceIndex); - } catch (e) { - this.setState({ error: RequestStatus.Error }); - } - this.setState({ loading: false }); - }; - updateName = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'name', e.target.value); - }; - updateRoom = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); - }; - componentWillMount() { - this.refreshDeviceProperty(); - } - - componentWillUpdate() { - this.refreshDeviceProperty(); - } - - render(props, { batteryLevel, loading, error }) { - return ( -
-
-
- {props.device.name} - {batteryLevel && ( -
-
- {batteryLevel}% - - - -
-
- )} -
-
-
-
-
-
- - - } - /> - -
-
- - -
-
- -
- {props.device && - props.device.features && - props.device.features.map(feature => ( - - -
- -
-
- ))} - {(!props.device.features || props.device.features.length === 0) && ( - - )} -
-
-
- - - - - -
-
-
-
-
-
- ); - } -} - -export default RflinkDeviceBox; +import { Text, Localizer } from 'preact-i18n'; +import { Component } from 'preact'; +import cx from 'classnames'; +import get from 'get-value'; +import { Link } from 'preact-router/match'; +import { DEVICE_FEATURE_CATEGORIES } from '../../../../../../../server/utils/constants'; +import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; + +class RflinkDeviceBox extends Component { + refreshDeviceProperty = () => { + if (!this.props.device.features) { + return null; + } + const batteryLevelDeviceFeature = this.props.device.features.find( + deviceFeature => deviceFeature.category === DEVICE_FEATURE_CATEGORIES.BATTERY + ); + const batteryLevel = get(batteryLevelDeviceFeature, 'last_value'); + this.setState({ + batteryLevel + }); + }; + saveDevice = async () => { + this.setState({ loading: true }); + try { + await this.props.saveDevice(this.props.device); + } catch (e) { + this.setState({ error: RequestStatus.Error }); + } + this.setState({ loading: false }); + }; + deleteDevice = async () => { + this.setState({ loading: true }); + try { + await this.props.deleteDevice(this.props.device, this.props.deviceIndex); + } catch (e) { + this.setState({ error: RequestStatus.Error }); + } + this.setState({ loading: false }); + }; + updateName = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'name', e.target.value); + }; + updateRoom = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); + }; + componentWillMount() { + this.refreshDeviceProperty(); + } + + componentWillUpdate() { + this.refreshDeviceProperty(); + } + + render(props, { batteryLevel, loading, error }) { + return ( +
+
+
+ {props.device.name} + {batteryLevel && ( +
+
+ {batteryLevel}% + + + +
+
+ )} +
+
+
+
+
+
+ + + } + /> + +
+
+ + +
+
+ +
+ {props.device && + props.device.features && + props.device.features.map(feature => ( + + +
+ +
+
+ ))} + {(!props.device.features || props.device.features.length === 0) && ( + + )} +
+
+
+ + + + + +
+
+
+
+
+
+ ); + } +} + +export default RflinkDeviceBox; diff --git a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx index bd4e7a9b89..cf483f4b20 100644 --- a/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DeviceForm.jsx @@ -1,112 +1,105 @@ -import { Text, Localizer } from 'preact-i18n'; -import { Component } from 'preact'; -import { DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; -import { DEVICE_MODELS_LIST } from '../../../../../../../server/utils/constants'; -import get from 'get-value'; - -class RflinkDeviceForm extends Component { - updateName = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'name', e.target.value); - }; - - updateRoom = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); - }; - - updateModel = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'model', e.target.value); - }; - - updateExternalId = e => { - this.props.updateDeviceProperty(this.props.deviceIndex, 'external_id', e.target.value); - }; - render({ ...props }) { - return ( -
-
- - - } - /> - -
- -
- - -
- - {(props.device.model === undefined || props.device.model.toLowerCase() !== 'milight') && ( -
- - -
- )} -
- -
- {props.device && - props.device.features && - props.device.features.map(feature => ( - - -
- -
-
- ))} - {(!props.device.features || props.device.features.length === 0) && ( - - )} -
-
- - - - - - -
- ); - } -} - -export default RflinkDeviceForm; +import { Text, Localizer } from 'preact-i18n'; +import { Component } from 'preact'; +import { DeviceFeatureCategoriesIcon } from '../../../../../utils/consts'; +import { DEVICE_MODELS_LIST } from '../../../../../../../server/utils/constants'; +import get from 'get-value'; + +class RflinkDeviceForm extends Component { + updateName = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'name', e.target.value); + }; + + updateRoom = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'room_id', e.target.value); + }; + + updateModel = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'model', e.target.value); + }; + + updateExternalId = e => { + this.props.updateDeviceProperty(this.props.deviceIndex, 'external_id', e.target.value); + }; + render({ ...props }) { + return ( +
+
+ + + } + /> + +
+ +
+ + +
+ + {(props.device.model === undefined || props.device.model.toLowerCase() !== 'milight') && ( +
+ + +
+ )} +
+ +
+ {props.device && + props.device.features && + props.device.features.map(feature => ( + + +
+ +
+
+ ))} + {(!props.device.features || props.device.features.length === 0) && ( + + )} +
+
+
+ ); + } +} + +export default RflinkDeviceForm; diff --git a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx index b11fe51566..d3f6e1f74f 100644 --- a/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx +++ b/front/src/routes/integration/all/rflink/device-page/DevicePage.jsx @@ -1,77 +1,77 @@ -import { Text, Localizer } from 'preact-i18n'; -import cx from 'classnames'; - -import { RequestStatus } from '../../../../../utils/consts'; -import Device from './Device'; -import { Link } from 'preact-router/match'; -import style from './style.css'; - -const NodeTab = ({ children, ...props }) => ( -
-
-

- -

-
- -
- - - - - } - onInput={props.debouncedSearch} - /> - -
- - - -
-
-
-
-
-
- {props.rflinkDevices && props.rflinkDevices.length === 0 && ( -
- -
- )} - {props.getRflinkDevicesStatus === RequestStatus.Getting &&
} -
- {props.rflinkDevices && - props.rflinkDevices.map((rflinkDevice, index) => ( - - ))} -
-
-
-
-
-); - -export default NodeTab; +import { Text, Localizer } from 'preact-i18n'; +import cx from 'classnames'; + +import { RequestStatus } from '../../../../../utils/consts'; +import Device from './Device'; +import { Link } from 'preact-router/match'; +import style from './style.css'; + +const NodeTab = ({ children, ...props }) => ( +
+
+

+ +

+
+ +
+ + + + + } + onInput={props.debouncedSearch} + /> + +
+ + + +
+
+
+
+
+
+ {props.rflinkDevices && props.rflinkDevices.length === 0 && ( +
+ +
+ )} + {props.getRflinkDevicesStatus === RequestStatus.Getting &&
} +
+ {props.rflinkDevices && + props.rflinkDevices.map((rflinkDevice, index) => ( + + ))} +
+
+
+
+
+); + +export default NodeTab; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx index c6e9888890..4795472cbd 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx @@ -1,206 +1,199 @@ -import { Text, Localizer } from 'preact-i18n'; -import { Component } from 'preact'; -import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_UNITS } from '../../../../../../../../server/utils/constants'; -import { DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; -import get from 'get-value'; - -const RflinkFeatureBox = ({ children, ...props }) => { - return ( -
-
-
- - -
-
-
- - - } - /> - -
- - -
-
- - - } - /> - -
-
- - {(props.feature.read_only === false || props.feature.read_only === undefined) && ( // Switch -
- - - } - /> - -
- - - )} - - - {props.feature.category === DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR && ( -
- - - - -
- )} -
-
- - - } - /> - -
-
- - - } - /> - -
-
- - - -
- -
-
-
-
- ); -}; - -class RflinkFeatureBoxComponent extends Component { - updateName = e => { - this.props.updateFeatureProperty(e, 'name', this.props.featureIndex); - }; - updateExternalId = e => { - this.props.updateFeatureProperty(e, 'external_id', this.props.featureIndex); - }; - updateMin = e => { - this.props.updateFeatureProperty(e, 'min', this.props.featureIndex); - }; - updateMax = e => { - this.props.updateFeatureProperty(e, 'max', this.props.featureIndex); - }; - updateUnit = e => { - this.props.updateFeatureProperty(e, 'unit', this.props.featureIndex); - }; - updateSwitchId = e => { - console.log(this.props); - this.props.feature.switchId = e.target.value; - let external = { - target : { - value: '' - } - }; - external.target.value = `rflink:${e.target.value}:switch:${this.props.feature.switchNumber}`; - this.updateExternalId(external); - - }; - updateSwitchNumber = e => { - console.log(this.props) - this.props.feature.switchNumber = e.target.value; - let external = { - target : { - value: '' - } - }; - external.target.value = `rflink:${this.props.feature.switchId}:switch:${e.target.value}`; - this.updateExternalId(external); - } - - deleteFeature = () => { - this.props.deleteFeature(this.props.featureIndex); - }; - render() { - return ( - - ); - } -} - -export default RflinkFeatureBoxComponent; +import { Text, Localizer } from 'preact-i18n'; +import { Component } from 'preact'; +import { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_UNITS } from '../../../../../../../../server/utils/constants'; +import { DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; +import get from 'get-value'; + +const RflinkFeatureBox = ({ children, ...props }) => { + return ( +
+
+
+ + +
+
+
+ + + } + /> + +
+ +
+
+ + + } + /> + +
+
+ + {(props.feature.read_only === false || props.feature.read_only === undefined) && ( // Switch +
+ + + } + /> + +
+ )} + + {props.feature.category === DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR && ( +
+ + + + +
+ )} +
+
+ + + } + /> + +
+
+ + + } + /> + +
+
+ +
+ +
+
+
+
+ ); +}; + +class RflinkFeatureBoxComponent extends Component { + updateName = e => { + this.props.updateFeatureProperty(e, 'name', this.props.featureIndex); + }; + updateExternalId = e => { + this.props.updateFeatureProperty(e, 'external_id', this.props.featureIndex); + }; + updateMin = e => { + this.props.updateFeatureProperty(e, 'min', this.props.featureIndex); + }; + updateMax = e => { + this.props.updateFeatureProperty(e, 'max', this.props.featureIndex); + }; + updateUnit = e => { + this.props.updateFeatureProperty(e, 'unit', this.props.featureIndex); + }; + updateSwitchId = e => { + console.log(this.props); + this.props.feature.switchId = e.target.value; + let external = { + target: { + value: '' + } + }; + external.target.value = `rflink:${e.target.value}:switch:${this.props.feature.switchNumber}`; + this.updateExternalId(external); + }; + updateSwitchNumber = e => { + console.log(this.props); + this.props.feature.switchNumber = e.target.value; + let external = { + target: { + value: '' + } + }; + external.target.value = `rflink:${this.props.feature.switchId}:switch:${e.target.value}`; + this.updateExternalId(external); + }; + + deleteFeature = () => { + this.props.deleteFeature(this.props.featureIndex); + }; + render() { + return ( + + ); + } +} + +export default RflinkFeatureBoxComponent; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx index 62fd707ffd..46e0aa901a 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/FeatureTab.jsx @@ -1,115 +1,105 @@ -import { Text, MarkupText } from 'preact-i18n'; -import { Link } from 'preact-router/match'; -import Feature from './Feature'; -import { DEVICE_FEATURE_CATEGORIES_LIST } from '../../../../../../../../server/utils/constants'; -import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; -import RflinkDeviceForm from '../DeviceForm'; -import cx from 'classnames'; - -const FeatureTab = ({ children, ...props }) => ( -
-
- - - -

- {(props.device && props.device.name) || } -

-
-
-
-
-
-
- -
- {props.saveStatus === RequestStatus.Error && ( -
- -
- )} - {props.saveStatus === RequestStatus.ConflictError && ( -
- -
- )} - {!props.loading && !props.device && ( -
-

- -

- - - -
- )} - {props.device && ( -
- - -
- - -
- - - - - - - - - - - -
- {props.device && - props.device.features.map((feature, index) => ( - - ))} -
- -
- - - - -
-
- )} -
-
-
-
-); - -export default FeatureTab; +import { Text, MarkupText } from 'preact-i18n'; +import { Link } from 'preact-router/match'; +import Feature from './Feature'; +import { DEVICE_FEATURE_CATEGORIES_LIST } from '../../../../../../../../server/utils/constants'; +import { RequestStatus, DeviceFeatureCategoriesIcon } from '../../../../../../utils/consts'; +import RflinkDeviceForm from '../DeviceForm'; +import cx from 'classnames'; + +const FeatureTab = ({ children, ...props }) => ( +
+
+ + + +

+ {(props.device && props.device.name) || } +

+
+
+
+
+
+
+ +
+ {props.saveStatus === RequestStatus.Error && ( +
+ +
+ )} + {props.saveStatus === RequestStatus.ConflictError && ( +
+ +
+ )} + {!props.loading && !props.device && ( +
+

+ +

+ + + +
+ )} + {props.device && ( +
+ + +
+ + +
+ +
+ {props.device && + props.device.features.map((feature, index) => ( + + ))} +
+ +
+ + + + +
+
+ )} +
+
+
+
+); + +export default FeatureTab; diff --git a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx index 0157e9e81b..ad50698462 100644 --- a/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx +++ b/front/src/routes/integration/all/rflink/settings-page/SettingsTab.jsx @@ -1,166 +1,142 @@ -import { Text } from 'preact-i18n'; -import get from 'get-value'; -import cx from 'classnames'; -import { STATE } from '../../../../../../../server/utils/constants'; - -const SettingsTab = ({ children, ...props }) => ( - -
-
-
-

- -

-
- -
-
-
-
-
-
- {get(props, 'rflinkStatus.connected') && ( -
- -
- )} - {!get(props, 'rflinkStatus.connected') && ( -
- -
- )} - {props.rflinkConnectionInProgress && ( -
- -
- )} - {props.rflinkFailed && ( -
- -
- )} -

- -

-
- - -
-
- - -
-
-
-
-
- - - - - - -
-
-

- -

-
-
- -
-
- -
- - - - - -
- - - - -
-
-
-
- - - - - -
-
-

- -

-
-
- -
-

- { - - `> ${get(props, 'rflinkStatus.lastCommand')} \n` - } -

-
-
- -
- -
-
-
-
-
-); - -export default SettingsTab; +import { Text } from 'preact-i18n'; +import get from 'get-value'; +import cx from 'classnames'; +import { STATE } from '../../../../../../../server/utils/constants'; + +const SettingsTab = ({ children, ...props }) => ( +
+
+
+

+ +

+
+ +
+
+
+
+
+
+ {get(props, 'rflinkStatus.connected') && ( +
+ +
+ )} + {!get(props, 'rflinkStatus.connected') && ( +
+ +
+ )} + {props.rflinkConnectionInProgress && ( +
+ +
+ )} + {props.rflinkFailed && ( +
+ +
+ )} +

+ +

+
+ + +
+
+ + +
+
+
+
+
+ +
+
+

+ +

+
+
+
+
+ +
+ + + + +
+ + + +
+
+
+
+ +
+
+

+ +

+
+
+ +
+

{`> ${get(props, 'rflinkStatus.lastCommand')} \n`}

+
+
+ +
+ +
+
+
+
+
+); + +export default SettingsTab; From 2c16089c0f25b051a582fe8b00582059ce11d67d Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:43:26 +0100 Subject: [PATCH 048/236] bug fixes --- front/src/config/demo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/config/demo.json b/front/src/config/demo.json index 59bf6fe1c3..82a68b1302 100644 --- a/front/src/config/demo.json +++ b/front/src/config/demo.json @@ -626,7 +626,7 @@ ] } ], - "get /api/v1/service/rflink/device" : [ + "get /api/v1/service/rflink/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", @@ -647,7 +647,7 @@ } ] } - ], + ], "get /api/v1/service/zwave/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", From 1d71ee1d1c114499afc3c274bc495f319514a093 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:44:21 +0100 Subject: [PATCH 049/236] bug fixes From 2c45178cf18cc9c5f46b29c542a160c3d420391c Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:44:48 +0100 Subject: [PATCH 050/236] constants upd --- server/utils/constants.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/server/utils/constants.js b/server/utils/constants.js index 7e451aace2..23ee0c8bff 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -233,7 +233,7 @@ const DEVICE_FEATURE_CATEGORIES = { const DEVICE_MODELS = { TRISTATE: 'Tristate', KAKU: 'Kaku', - NEWKAKU: 'Newkaku', + NEWKAKU: 'Newkaku', HOMEEASY: 'Homeeasy', CONRAD: 'Conrad rsl2', BLYSS: 'Blyss', @@ -248,14 +248,7 @@ const DEVICE_MODELS = { SELECTPLUS: 'Selectplus', DELTRONIC: 'Deltronic', MERTIK: 'Mertik', - EV1527 : 'Ev1527', - - - - - - - + EV1527: 'Ev1527', }; const DEVICE_FEATURE_TYPES = { @@ -382,7 +375,7 @@ const WEBSOCKET_MESSAGE_TYPES = { CONNECTED: 'mqtt.connected', ERROR: 'mqtt.error', }, - RFLINK : { + RFLINK: { DRIVER_READY: 'rflink.driver-ready', }, XIAOMI: { From 22a058f6addd8fabfc14ee1e608887473171fe9f Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:48:14 +0100 Subject: [PATCH 051/236] prettier --- front/src/components/app.jsx | 448 +++++++++++++++++------------------ 1 file changed, 222 insertions(+), 226 deletions(-) diff --git a/front/src/components/app.jsx b/front/src/components/app.jsx index 32bfd42888..a5f8d31af6 100644 --- a/front/src/components/app.jsx +++ b/front/src/components/app.jsx @@ -1,226 +1,222 @@ -import { h, Component } from 'preact'; -import { Router } from 'preact-router'; -import createStore from 'unistore'; -import config from '../../config'; -import { Provider, connect } from 'unistore/preact'; -import { IntlProvider } from 'preact-i18n'; -import translationEn from '../config/i18n/en.json'; -import actions from '../actions/main'; - -import { getDefaultState } from '../utils/getDefaultState'; - -import Header from './header'; -import Layout from './layout'; -import Redirect from './router/Redirect'; -import Login from '../routes/login'; -import Error from '../routes/error'; -import ForgotPassword from '../routes/forgot-password'; -import ResetPassword from '../routes/reset-password'; -import LoginGateway from '../routes/login-gateway'; -import LinkGatewayUser from '../routes/gateway-setup'; -import SignupGateway from '../routes/signup-gateway'; -import SubscribeGateway from '../routes/subscribe-gateway'; -import ConfigureTwoFactorGateway from '../routes/gateway-configure-two-factor'; -import GatewayForgotPassword from '../routes/gateway-forgot-password'; -import GatewayResetPassword from '../routes/gateway-reset-password'; -import GatewayConfirmEmail from '../routes/gateway-confirm-email'; - -import SignupWelcomePage from '../routes/signup/1-welcome'; -import SignupCreateAccountLocal from '../routes/signup/2-create-account-local'; -import SignupCreateAccountGladysGateway from '../routes/signup/2-create-account-gladys-gateway'; -import SignupPreferences from '../routes/signup/3-preferences'; -import SignupConfigureHouse from '../routes/signup/4-configure-house'; -import SignupSuccess from '../routes/signup/5-success'; - -import Dashboard from '../routes/dashboard'; -import Device from '../routes/device'; -import IntegrationPage from '../routes/integration'; -import ChatPage from '../routes/chat'; -import MapPage from '../routes/map'; -import CalendarPage from '../routes/calendar'; -import ScenePage from '../routes/scene'; -import NewScenePage from '../routes/scene/new-scene'; -import EditScenePage from '../routes/scene/edit-scene'; -import TriggerPage from '../routes/trigger'; -import ProfilePage from '../routes/profile'; -import SettingsSessionPage from '../routes/settings/settings-session'; -import SettingsHousePage from '../routes/settings/settings-house'; -import SettingsAdvancedPage from '../routes/settings/settings-advanced'; -import SettingsSystemPage from '../routes/settings/settings-system'; -import SettingsGateway from '../routes/settings/settings-gateway'; -import SettingsBackup from '../routes/settings/settings-backup'; -import SettingsBilling from '../routes/settings/settings-billing'; -import SettingsGatewayUsers from '../routes/settings/settings-gateway-users'; -import SettingsGatewayOpenApi from '../routes/settings/settings-gateway-open-api'; - -// Integrations -import TelegramPage from '../routes/integration/all/telegram'; -import DarkSkyPage from '../routes/integration/all/darksky'; -import PhilipsHueSetupPage from '../routes/integration/all/philips-hue/setup-page'; -import PhilipsHueDevicePage from '../routes/integration/all/philips-hue/device-page'; -import ZwaveNodePage from '../routes/integration/all/zwave/node-page'; -import ZwaveNetworkPage from '../routes/integration/all/zwave/network-page'; -import ZwaveSettingsPage from '../routes/integration/all/zwave/settings-page'; -import ZwaveSetupPage from '../routes/integration/all/zwave/setup-page'; -import ZwaveNodeOperationPage from '../routes/integration/all/zwave/node-operation-page'; -import ZwaveEditPage from '../routes/integration/all/zwave/edit-page'; -import RtspCameraPage from '../routes/integration/all/rtsp-camera'; -import XiaomiPage from '../routes/integration/all/xiaomi'; -import EditXiaomiPage from '../routes/integration/all/xiaomi/edit-page'; -import RflinkDevicePage from '../routes/integration/all/rflink/device-page'; -import RflinkSettingsPage from '../routes/integration/all/rflink/settings-page'; -import RflinkEditPage from '../routes/integration/all/rflink/device-page/setup'; - - -// MQTT integration -import MqttDevicePage from '../routes/integration/all/mqtt/device-page'; -import MqttDeviceSetupPage from '../routes/integration/all/mqtt/device-page/setup'; -import MqttSetupPage from '../routes/integration/all/mqtt/setup-page'; - -// Sonoff -import SonoffPage from '../routes/integration/all/sonoff/device-page'; -import SonoffDiscoverPage from '../routes/integration/all/sonoff/discover-page'; - -const defaultState = getDefaultState(); -const store = createStore(defaultState); - -const AppRouter = connect( - 'currentUrl,user,profilePicture,showDropDown,showCollapsedMenu', - actions -)(props => ( -
- -
- - - {/** ROUTE WHICH ARE DIFFERENT IN GATEWAY MODE */} - {config.gatewayMode ? : } - {config.gatewayMode ? ( - - ) : ( - - )} - {config.gatewayMode ? ( - - ) : ( - - )} - {config.gatewayMode ? : } - {config.gatewayMode ? : } - {config.gatewayMode ? : } - {config.gatewayMode ? ( - - ) : ( - - )} - {config.gatewayMode ? : } - {config.gatewayMode ? : } - {config.gatewayMode ? ( - - ) : ( - - )} - {config.gatewayMode ? ( - - ) : ( - - )} - - {!config.gatewayMode ? : } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-)); - -@connect('', actions) -class MainApp extends Component { - componentWillMount() { - this.props.checkSession(); - } - - render({}, {}) { - return ; - } -} - -const App = () => ( - - - - - -); - -export default App; +import { h, Component } from 'preact'; +import { Router } from 'preact-router'; +import createStore from 'unistore'; +import config from '../../config'; +import { Provider, connect } from 'unistore/preact'; +import { IntlProvider } from 'preact-i18n'; +import translationEn from '../config/i18n/en.json'; +import actions from '../actions/main'; + +import { getDefaultState } from '../utils/getDefaultState'; + +import Header from './header'; +import Layout from './layout'; +import Redirect from './router/Redirect'; +import Login from '../routes/login'; +import Error from '../routes/error'; +import ForgotPassword from '../routes/forgot-password'; +import ResetPassword from '../routes/reset-password'; +import LoginGateway from '../routes/login-gateway'; +import LinkGatewayUser from '../routes/gateway-setup'; +import SignupGateway from '../routes/signup-gateway'; +import SubscribeGateway from '../routes/subscribe-gateway'; +import ConfigureTwoFactorGateway from '../routes/gateway-configure-two-factor'; +import GatewayForgotPassword from '../routes/gateway-forgot-password'; +import GatewayResetPassword from '../routes/gateway-reset-password'; +import GatewayConfirmEmail from '../routes/gateway-confirm-email'; + +import SignupWelcomePage from '../routes/signup/1-welcome'; +import SignupCreateAccountLocal from '../routes/signup/2-create-account-local'; +import SignupCreateAccountGladysGateway from '../routes/signup/2-create-account-gladys-gateway'; +import SignupPreferences from '../routes/signup/3-preferences'; +import SignupConfigureHouse from '../routes/signup/4-configure-house'; +import SignupSuccess from '../routes/signup/5-success'; + +import Dashboard from '../routes/dashboard'; +import Device from '../routes/device'; +import IntegrationPage from '../routes/integration'; +import ChatPage from '../routes/chat'; +import MapPage from '../routes/map'; +import CalendarPage from '../routes/calendar'; +import ScenePage from '../routes/scene'; +import NewScenePage from '../routes/scene/new-scene'; +import EditScenePage from '../routes/scene/edit-scene'; +import TriggerPage from '../routes/trigger'; +import ProfilePage from '../routes/profile'; +import SettingsSessionPage from '../routes/settings/settings-session'; +import SettingsHousePage from '../routes/settings/settings-house'; +import SettingsAdvancedPage from '../routes/settings/settings-advanced'; +import SettingsSystemPage from '../routes/settings/settings-system'; +import SettingsGateway from '../routes/settings/settings-gateway'; +import SettingsBackup from '../routes/settings/settings-backup'; +import SettingsBilling from '../routes/settings/settings-billing'; +import SettingsGatewayUsers from '../routes/settings/settings-gateway-users'; +import SettingsGatewayOpenApi from '../routes/settings/settings-gateway-open-api'; + +// Integrations +import TelegramPage from '../routes/integration/all/telegram'; +import DarkSkyPage from '../routes/integration/all/darksky'; +import PhilipsHueSetupPage from '../routes/integration/all/philips-hue/setup-page'; +import PhilipsHueDevicePage from '../routes/integration/all/philips-hue/device-page'; +import ZwaveNodePage from '../routes/integration/all/zwave/node-page'; +import ZwaveNetworkPage from '../routes/integration/all/zwave/network-page'; +import ZwaveSettingsPage from '../routes/integration/all/zwave/settings-page'; +import ZwaveSetupPage from '../routes/integration/all/zwave/setup-page'; +import ZwaveNodeOperationPage from '../routes/integration/all/zwave/node-operation-page'; +import ZwaveEditPage from '../routes/integration/all/zwave/edit-page'; +import RtspCameraPage from '../routes/integration/all/rtsp-camera'; +import XiaomiPage from '../routes/integration/all/xiaomi'; +import EditXiaomiPage from '../routes/integration/all/xiaomi/edit-page'; +import RflinkDevicePage from '../routes/integration/all/rflink/device-page'; +import RflinkSettingsPage from '../routes/integration/all/rflink/settings-page'; +import RflinkEditPage from '../routes/integration/all/rflink/device-page/setup'; + +// MQTT integration +import MqttDevicePage from '../routes/integration/all/mqtt/device-page'; +import MqttDeviceSetupPage from '../routes/integration/all/mqtt/device-page/setup'; +import MqttSetupPage from '../routes/integration/all/mqtt/setup-page'; + +// Sonoff +import SonoffPage from '../routes/integration/all/sonoff/device-page'; +import SonoffDiscoverPage from '../routes/integration/all/sonoff/discover-page'; + +const defaultState = getDefaultState(); +const store = createStore(defaultState); + +const AppRouter = connect( + 'currentUrl,user,profilePicture,showDropDown,showCollapsedMenu', + actions +)(props => ( +
+ +
+ + + {/** ROUTE WHICH ARE DIFFERENT IN GATEWAY MODE */} + {config.gatewayMode ? : } + {config.gatewayMode ? ( + + ) : ( + + )} + {config.gatewayMode ? ( + + ) : ( + + )} + {config.gatewayMode ? : } + {config.gatewayMode ? : } + {config.gatewayMode ? : } + {config.gatewayMode ? ( + + ) : ( + + )} + {config.gatewayMode ? : } + {config.gatewayMode ? : } + {config.gatewayMode ? ( + + ) : ( + + )} + {config.gatewayMode ? ( + + ) : ( + + )} + + {!config.gatewayMode ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+)); + +@connect('', actions) +class MainApp extends Component { + componentWillMount() { + this.props.checkSession(); + } + + render({}, {}) { + return ; + } +} + +const App = () => ( + + + + + +); + +export default App; From 7a2b221d1bc886a76cc78a09aac4144f9e131f72 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:49:27 +0100 Subject: [PATCH 052/236] prettier From eadd6b35994609c9d831664aad77df1898f46f24 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:51:28 +0100 Subject: [PATCH 053/236] prettier From 803e50b828f7f0086d02c9738e7d2af6810d49d7 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:53:37 +0100 Subject: [PATCH 054/236] Delete FoundDevices.jsx --- .../all/rflink/device-page/FoundDevices.jsx | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx diff --git a/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx b/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx deleted file mode 100644 index d6b5931f0b..0000000000 --- a/front/src/routes/integration/all/rflink/device-page/FoundDevices.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Text } from 'preact-i18n'; -import cx from 'classnames'; - -import style from './style.css'; -import { RequestStatus } from '../../../../../utils/consts'; - -const createDevice = (props, device) => () => { - props.createDevice(device); -}; - -const FoundDevices = ({ children, ...props }) => ( -
-
-

- -

-
-
-
-
-
- {props.getRflinkDevicesStatus === RequestStatus.Getting &&
} -
- {props.rflinkNewDevices && props.rflinkNewDevices.length === 0 && ( -
-
- -
-
- )} - {props.rflinkNewDevices && - props.rflinkNewDevices.map((device, index) => ( -
-
-
-

{device.name}

-
-
- {!device.not_handled && ( - - )} - {device.not_handled && ( -
- -
- )} -
-
-
- ))} - {props.rflinkDevices && props.rflinkDevices.length === 0 && ( - - )} -
-
-
-
-
-); - -export default FoundDevices; From 3c6d718b5f58013deb80babe0caf83ac0d89699f Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:55:28 +0100 Subject: [PATCH 055/236] prettier --- .../services/rflink/lib/devicesToTest.test.js | 63 +++++----- .../services/rflink/lib/rflinkManager.test.js | 119 +++++++++--------- server/test/services/rflink/rflink.test.js | 63 +++++----- .../test/services/rflink/rflinkMock.test.js | 38 +++--- 4 files changed, 137 insertions(+), 146 deletions(-) diff --git a/server/test/services/rflink/lib/devicesToTest.test.js b/server/test/services/rflink/lib/devicesToTest.test.js index 642a32f30c..7bc21a448f 100644 --- a/server/test/services/rflink/lib/devicesToTest.test.js +++ b/server/test/services/rflink/lib/devicesToTest.test.js @@ -1,33 +1,30 @@ -const { - DEVICE_FEATURE_CATEGORIES, - DEVICE_FEATURE_TYPES, - } = require('../../../../utils/constants'); - -const DEVICES = [ - { - service_id : 'a810b8db-6d04-4697-bed3-c4b72c996279', - name : `Prise `, - selector : `rflink:86aa7:11`, - external_id: `rflink:86aa7:11`, - model : 'Tristate', - should_poll : false, - features : [ - { - name : 'switch', - selector : `rflink:86aa7:switch:11`, - external_id : `rflink:86aa7:switch:11`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.SWITCH, - type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 1, - }, - ] - }, - {}, -]; - -module.exports = DEVICES; \ No newline at end of file +const { DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../../../utils/constants'); + +const DEVICES = [ + { + service_id: 'a810b8db-6d04-4697-bed3-c4b72c996279', + name: `Prise `, + selector: `rflink:86aa7:11`, + external_id: `rflink:86aa7:11`, + model: 'Tristate', + should_poll: false, + features: [ + { + name: 'switch', + selector: `rflink:86aa7:switch:11`, + external_id: `rflink:86aa7:switch:11`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.SWITCH, + type: DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }, + ], + }, + {}, +]; + +module.exports = DEVICES; diff --git a/server/test/services/rflink/lib/rflinkManager.test.js b/server/test/services/rflink/lib/rflinkManager.test.js index f54db90a27..b917ec4f7c 100644 --- a/server/test/services/rflink/lib/rflinkManager.test.js +++ b/server/test/services/rflink/lib/rflinkManager.test.js @@ -1,62 +1,57 @@ -/* eslint-disable no-restricted-syntax */ -const { expect } = require('chai'); -const { assert } = require('sinon'); -const EventEmitter = require('events'); -const {DEVICE_FEATURE_CATEGORIES} = require('../../../../utils/constants'); - - -const RflinkManager = require('../../../../services/rflink/lib'); -const DEVICES = require('./devicesToTest.test'); -const RflinkMock = require('../rflinkMock.test'); - -describe('Rflink Manager Commands', () => { - const gladys = { - event: new EventEmitter(), - }; - const rflinkManager = new RflinkManager(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279'); - rflinkManager.connected = true; - - it('should connect to the Rflink Gateway', () => { - rflinkManager.connect('COM8'); - }); - - it('should disconnect from the Rflink Gateway', () => { - rflinkManager.disconnect(); - }); - - it('should listen', () => { - rflinkManager.listen(); - }); - - it('should get devices', ()=> { - rflinkManager.getDevices(); - }); - - it('should pair Milights', () => { - rflinkManager.pair('F746', '1'); - }); - - it('should unpair Milights', () => { - rflinkManager.unpair('F746', '1'); - }); - - DEVICES.forEach((device) => { - device.features.forEach((feature) => { - if (feature !==undefined) { - it('should change value of a device', () => { - if (feature.read_only === false) { // it's not a sensor - if (feature.type === 'binary' || feature.type === 'decimal') { - return rflinkManager.setValue(device, feature, 1); - } - return 'error'; - } - }); - } - }); - }); - - - - - }); - +/* eslint-disable consistent-return */ +/* eslint-disable max-len */ +/* eslint-disable no-restricted-syntax */ +const EventEmitter = require('events'); +// const {DEVICE_FEATURE_CATEGORIES} = require('../../../../utils/constants'); + +const RflinkManager = require('../../../../services/rflink/lib'); +const DEVICES = require('./devicesToTest.test'); +// const RflinkMock = require('../rflinkMock.test'); + +describe('Rflink Manager Commands', () => { + const gladys = { + event: new EventEmitter(), + }; + const rflinkManager = new RflinkManager(gladys, 'a810b8db-6d04-4697-bed3-c4b72c996279'); + rflinkManager.connected = true; + + it('should connect to the Rflink Gateway', () => { + rflinkManager.connect('COM8'); + }); + + it('should disconnect from the Rflink Gateway', () => { + rflinkManager.disconnect(); + }); + + it('should listen', () => { + rflinkManager.listen(); + }); + + it('should get devices', () => { + rflinkManager.getDevices(); + }); + + it('should pair Milights', () => { + rflinkManager.pair('F746', '1'); + }); + + it('should unpair Milights', () => { + rflinkManager.unpair('F746', '1'); + }); + + DEVICES.forEach((device) => { + device.features.forEach((feature) => { + if (feature !== undefined) { + it('should change value of a device', () => { + if (feature.read_only === false) { + // it's not a sensor + if (feature.type === 'binary' || feature.type === 'decimal') { + return rflinkManager.setValue(device, feature, 1); + } + return 'error'; + } + }); + } + }); + }); +}); diff --git a/server/test/services/rflink/rflink.test.js b/server/test/services/rflink/rflink.test.js index aecfdd43e3..48ad0c059f 100644 --- a/server/test/services/rflink/rflink.test.js +++ b/server/test/services/rflink/rflink.test.js @@ -1,32 +1,31 @@ -const { expect } = require('chai'); -const EventEmitter = require('events'); -const proxyquire = require('proxyquire').noCallThru(); -const SerialPort = require('serialport'); -const RflinkMock = require('./rflinkMock.test'); - - -const RflinkService = proxyquire('../../../services/rflink/index', { - 'SerialPort': SerialPort, -}); - -const gladys = { - event: new EventEmitter(), - variable: { - getValue: () => Promise.resolve('test'), - }, -}; - -describe('rflinkService', () => { - const rflinkService = RflinkService(gladys, 'be86c4db-489f-466c-aeea-1e262c4ee720'); - it('should have controllers', () => { - expect(rflinkService) - .to.have.property('controllers') - .and.be.instanceOf(Object); - }); - it('should start service', async () => { - await rflinkService.start(); - }); - it('should stop service', async () => { - await rflinkService.stop(); - }); -}); +const { expect } = require('chai'); +const EventEmitter = require('events'); +const proxyquire = require('proxyquire').noCallThru(); +const SerialPort = require('serialport'); +// const RflinkMock = require('./rflinkMock.test'); + +const RflinkService = proxyquire('../../../services/rflink/index', { + SerialPort, +}); + +const gladys = { + event: new EventEmitter(), + variable: { + getValue: () => Promise.resolve('test'), + }, +}; + +describe('rflinkService', () => { + const rflinkService = RflinkService(gladys, 'be86c4db-489f-466c-aeea-1e262c4ee720'); + it('should have controllers', () => { + expect(rflinkService) + .to.have.property('controllers') + .and.be.instanceOf(Object); + }); + it('should start service', async () => { + await rflinkService.start(); + }); + it('should stop service', async () => { + await rflinkService.stop(); + }); +}); diff --git a/server/test/services/rflink/rflinkMock.test.js b/server/test/services/rflink/rflinkMock.test.js index e1f63608e6..3672caabb2 100644 --- a/server/test/services/rflink/rflinkMock.test.js +++ b/server/test/services/rflink/rflinkMock.test.js @@ -1,19 +1,19 @@ -const { fake } = require('sinon'); -const EventEmitter = require('events'); - -const Rflink = function Rflink(options) {}; - -Rflink.prototype = Object.create(new EventEmitter()); - -Rflink.prototype.message = fake.returns(null); -Rflink.prototype.newValue = fake.returns(null); -Rflink.prototype.addDevice = fake.returns(null); -Rflink.prototype.setValue = fake.returns(null); -Rflink.prototype.connect = fake.returns(null); -Rflink.prototype.disconnect = fake.returns(null); -Rflink.prototype.listen = fake.returns(null); -Rflink.prototype.getDevices = fake.returns(null); -Rflink.prototype.pair = fake.returns(null); -Rflink.prototype.unpair = fake.returns(null); - -module.exports = Rflink; +const { fake } = require('sinon'); +const EventEmitter = require('events'); + +const Rflink = function Rflink(options) {}; + +Rflink.prototype = Object.create(new EventEmitter()); + +Rflink.prototype.message = fake.returns(null); +Rflink.prototype.newValue = fake.returns(null); +Rflink.prototype.addDevice = fake.returns(null); +Rflink.prototype.setValue = fake.returns(null); +Rflink.prototype.connect = fake.returns(null); +Rflink.prototype.disconnect = fake.returns(null); +Rflink.prototype.listen = fake.returns(null); +Rflink.prototype.getDevices = fake.returns(null); +Rflink.prototype.pair = fake.returns(null); +Rflink.prototype.unpair = fake.returns(null); + +module.exports = Rflink; From 895a5e9c0611216fc7fead3b32301cd557860dda Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:26:53 +0100 Subject: [PATCH 056/236] serialport => 8.0.6 --- .../rflink/lib/commands/rflink.setValue.js | 16 ++++++++-- .../rflink/lib/events/rflink.message.js | 25 ++++++++++++++-- .../rflink/lib/events/rflink.newValue.js | 29 +++++++++++++++++-- server/services/rflink/package.json | 4 +-- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js index 82e6e0b956..b85310894d 100644 --- a/server/services/rflink/lib/commands/rflink.setValue.js +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -1,4 +1,7 @@ const ObjToRF = require('../../api/rflink.parse.ObjToRF'); +const { + DEVICE_FEATURE_CATEGORIES +} = require('../../../../utils/constants'); const logger = require('../../../../utils/logger'); /** * @description send a message to change a device's value @@ -20,11 +23,20 @@ function setValue(device, deviceFeature, state) { switch (state) { case 0: case false: - value = 'OFF'; + if (deviceFeature.category === DEVICE_FEATURE_CATEGORIES.SWITCH) { + value = 'OFF'; + } else if (deviceFeature.category === DEVICE_FEATURE_CATEGORIES.BUTTON) { + value = 'DOWN'; + } + break; case 1: case true: - value = 'ON'; + if (deviceFeature.category === DEVICE_FEATURE_CATEGORIES.SWITCH) { + value = 'ON'; + } else if (deviceFeature.category === DEVICE_FEATURE_CATEGORIES.BUTTON) { + value = 'UP'; + } break; default: diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 25ab122253..db5fec7adc 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -218,6 +218,27 @@ function message(msgRF) { }); } + if ( + msg.switch !== undefined && + msg.rgwb === undefined && + (msg.cmd === 'UP' || msg.cmd === 'DOWN') + ) { + newDevice.name += 'switch'; + newDevice.features.push({ + name: 'switch', + selector: `rflink:${msg.id}:switch:${msg.switch}`, + external_id: `rflink:${msg.id}:switch:${msg.switch}`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.BUTTON, + type: DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }); + } + if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { newDevice.selector = `rflink:milight:${msg.id}:${msg.switch}`; newDevice.external_id = `rflink:milight:${msg.id}:${msg.switch}`; @@ -328,10 +349,10 @@ function message(msgRF) { this.newValue(msg, 'wind-speed', msg.wings); } if ( - (msg.switch !== undefined && msg.cmd === 'ON') || + (msg.switch !== undefined) && (msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || - msg.cmd === 'ALLOFF' + msg.cmd === 'ALLOFF') ) { this.newValue(msg, 'switch', msg.cmd); } diff --git a/server/services/rflink/lib/events/rflink.newValue.js b/server/services/rflink/lib/events/rflink.newValue.js index ab69dfdfce..5f3e14c651 100644 --- a/server/services/rflink/lib/events/rflink.newValue.js +++ b/server/services/rflink/lib/events/rflink.newValue.js @@ -9,12 +9,37 @@ const { EVENTS } = require('../../../../utils/constants'); * newValue(Object, 'temperature', 30) */ function newValue(device, deviceFeature, state) { - logger.debug(`RFlink : value ${deviceFeature} of device ${device} changed to ${state}`); + logger.debug(`RFlink : value ${deviceFeature} of device rflink:${device.id}:${deviceFeature}:${device.switch} changed to ${state}`); + let value = state; + switch (state) { + case 'ON': + case 'ALLON': + case 'UP' : + value = 1; + break; + case 'OFF': + case 'ALLOFF': + case 'DOWN' : + value = 0; + break; + default : + value = state; + break; + } +if (device.switch === undefined) { this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, - state, + state : value, }); +} else { + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}:${device.switch}`, + state : value, + }); +} + + } module.exports = { diff --git a/server/services/rflink/package.json b/server/services/rflink/package.json index 35d558c142..af400b9c61 100644 --- a/server/services/rflink/package.json +++ b/server/services/rflink/package.json @@ -15,7 +15,7 @@ "arm64" ], "dependencies": { - "serialport": "8.0.7", - "@serialport/parser-readline": "8.0.7" + "serialport": "8.0.6", + "@serialport/parser-readline": "8.0.6" } } From 66efeffeea831677eda856166a7517d5ba6d1890 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:27:49 +0100 Subject: [PATCH 057/236] i18n upd From 041e913f610ffdfc4131881cb24a0dac2ebed122 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:32:25 +0100 Subject: [PATCH 058/236] prettier --- .../rflink/lib/commands/rflink.setValue.js | 6 +- .../rflink/lib/events/rflink.message.js | 12 +--- .../rflink/lib/events/rflink.newValue.js | 60 +++++++++---------- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js index b85310894d..8d63d527cc 100644 --- a/server/services/rflink/lib/commands/rflink.setValue.js +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -1,7 +1,5 @@ const ObjToRF = require('../../api/rflink.parse.ObjToRF'); -const { - DEVICE_FEATURE_CATEGORIES -} = require('../../../../utils/constants'); +const { DEVICE_FEATURE_CATEGORIES } = require('../../../../utils/constants'); const logger = require('../../../../utils/logger'); /** * @description send a message to change a device's value @@ -28,7 +26,7 @@ function setValue(device, deviceFeature, state) { } else if (deviceFeature.category === DEVICE_FEATURE_CATEGORIES.BUTTON) { value = 'DOWN'; } - + break; case 1: case true: diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index db5fec7adc..ddf2e0e20d 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -218,11 +218,7 @@ function message(msgRF) { }); } - if ( - msg.switch !== undefined && - msg.rgwb === undefined && - (msg.cmd === 'UP' || msg.cmd === 'DOWN') - ) { + if (msg.switch !== undefined && msg.rgwb === undefined && (msg.cmd === 'UP' || msg.cmd === 'DOWN')) { newDevice.name += 'switch'; newDevice.features.push({ name: 'switch', @@ -349,10 +345,8 @@ function message(msgRF) { this.newValue(msg, 'wind-speed', msg.wings); } if ( - (msg.switch !== undefined) && (msg.cmd === 'ON' || - msg.cmd === 'OFF' || - msg.cmd === 'ALLON' || - msg.cmd === 'ALLOFF') + msg.switch !== undefined && + (msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') ) { this.newValue(msg, 'switch', msg.cmd); } diff --git a/server/services/rflink/lib/events/rflink.newValue.js b/server/services/rflink/lib/events/rflink.newValue.js index 5f3e14c651..709ddc2a8d 100644 --- a/server/services/rflink/lib/events/rflink.newValue.js +++ b/server/services/rflink/lib/events/rflink.newValue.js @@ -9,37 +9,37 @@ const { EVENTS } = require('../../../../utils/constants'); * newValue(Object, 'temperature', 30) */ function newValue(device, deviceFeature, state) { - logger.debug(`RFlink : value ${deviceFeature} of device rflink:${device.id}:${deviceFeature}:${device.switch} changed to ${state}`); - let value = state; - switch (state) { - case 'ON': - case 'ALLON': - case 'UP' : - value = 1; - break; - case 'OFF': - case 'ALLOFF': - case 'DOWN' : - value = 0; - break; - default : - value = state; - break; - } - -if (device.switch === undefined) { - this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { - device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, - state : value, - }); -} else { - this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { - device_feature_external_id: `rflink:${device.id}:${deviceFeature}:${device.switch}`, - state : value, - }); -} - + logger.debug( + `RFlink : value ${deviceFeature} of device rflink:${device.id}:${deviceFeature}:${device.switch} changed to ${state}`, + ); + let value = state; + switch (state) { + case 'ON': + case 'ALLON': + case 'UP': + value = 1; + break; + case 'OFF': + case 'ALLOFF': + case 'DOWN': + value = 0; + break; + default: + value = state; + break; + } + if (device.switch === undefined) { + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, + state: value, + }); + } else { + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}:${device.switch}`, + state: value, + }); + } } module.exports = { From 783e36d3b9b957fe08fe1ecd487299845ccb5e28 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:44:26 +0100 Subject: [PATCH 059/236] tests --- server/test/services/rflink/lib/rflinkManager.test.js | 6 ++++++ server/test/services/rflink/rflink.test.js | 5 ++--- server/test/services/rflink/rflinkMock.test.js | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/server/test/services/rflink/lib/rflinkManager.test.js b/server/test/services/rflink/lib/rflinkManager.test.js index b917ec4f7c..4ba7334ad0 100644 --- a/server/test/services/rflink/lib/rflinkManager.test.js +++ b/server/test/services/rflink/lib/rflinkManager.test.js @@ -1,6 +1,8 @@ /* eslint-disable consistent-return */ /* eslint-disable max-len */ /* eslint-disable no-restricted-syntax */ +/* eslint-disable import/no-unresolved */ + const EventEmitter = require('events'); // const {DEVICE_FEATURE_CATEGORIES} = require('../../../../utils/constants'); @@ -51,6 +53,10 @@ describe('Rflink Manager Commands', () => { return 'error'; } }); + + it('should add devices', () => { + rflinkManager.addDevice(device); + }); } }); }); diff --git a/server/test/services/rflink/rflink.test.js b/server/test/services/rflink/rflink.test.js index 48ad0c059f..5fe1249b14 100644 --- a/server/test/services/rflink/rflink.test.js +++ b/server/test/services/rflink/rflink.test.js @@ -1,11 +1,10 @@ const { expect } = require('chai'); const EventEmitter = require('events'); const proxyquire = require('proxyquire').noCallThru(); -const SerialPort = require('serialport'); -// const RflinkMock = require('./rflinkMock.test'); + const RflinkMock = require('./rflinkMock.test'); const RflinkService = proxyquire('../../../services/rflink/index', { - SerialPort, + SerialPort : RflinkMock, }); const gladys = { diff --git a/server/test/services/rflink/rflinkMock.test.js b/server/test/services/rflink/rflinkMock.test.js index 3672caabb2..0b0519c753 100644 --- a/server/test/services/rflink/rflinkMock.test.js +++ b/server/test/services/rflink/rflinkMock.test.js @@ -15,5 +15,6 @@ Rflink.prototype.listen = fake.returns(null); Rflink.prototype.getDevices = fake.returns(null); Rflink.prototype.pair = fake.returns(null); Rflink.prototype.unpair = fake.returns(null); +Rflink.prototype.write = fake.returns(null); module.exports = Rflink; From 790aa6e999f7acba1fa02a1ae56458784ece10fa Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 20 Mar 2020 15:48:09 +0100 Subject: [PATCH 060/236] prettier for tests --- server/test/services/rflink/rflink.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/test/services/rflink/rflink.test.js b/server/test/services/rflink/rflink.test.js index 5fe1249b14..355f34381a 100644 --- a/server/test/services/rflink/rflink.test.js +++ b/server/test/services/rflink/rflink.test.js @@ -1,10 +1,10 @@ const { expect } = require('chai'); const EventEmitter = require('events'); const proxyquire = require('proxyquire').noCallThru(); - const RflinkMock = require('./rflinkMock.test'); +const RflinkMock = require('./rflinkMock.test'); const RflinkService = proxyquire('../../../services/rflink/index', { - SerialPort : RflinkMock, + SerialPort: RflinkMock, }); const gladys = { From 6cd6d35b8f28c94dad5c1246ad81ee198d64c2b5 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:32:19 +0100 Subject: [PATCH 061/236] const upd --- server/utils/constants.js | 11 +++++++++-- server/utils/coreErrors.js | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/server/utils/constants.js b/server/utils/constants.js index 23ee0c8bff..affa9b57a3 100644 --- a/server/utils/constants.js +++ b/server/utils/constants.js @@ -88,6 +88,9 @@ const EVENTS = { HUE_CHANGED: 'light.hue-changed', SATURATION_CHANGED: 'light.saturation-changed', }, + TIME: { + CHANGED: 'time.changed', + }, TRIGGERS: { CHECK: 'trigger.check', }, @@ -173,9 +176,11 @@ const CONDITIONS = { const ACTIONS = { DEVICE: { SET_VALUE: 'device.set-value', + GET_VALUE: 'device.get-value', }, LIGHT: { TURN_ON: 'light.turn-on', + TURN_OFF: 'light.turn-off', }, TIME: { DELAY: 'delay', @@ -190,6 +195,9 @@ const ACTIONS = { MESSAGE: { SEND: 'message.send', }, + CONDITION: { + ONLY_CONTINUE_IF: 'condition.only-continue-if', + }, }; const INTENTS = { @@ -260,7 +268,6 @@ const DEVICE_FEATURE_TYPES = { COLOR: 'color', TEMPERATURE: 'temperature', POWER: 'power', - MODE: 'mode', }, SENSOR: { DECIMAL: 'decimal', @@ -445,6 +452,7 @@ module.exports.ACTIONS_STATUS = ACTIONS_STATUS; module.exports.USER_ROLE = USER_ROLE; module.exports.AVAILABLE_LANGUAGES = AVAILABLE_LANGUAGES; module.exports.SESSION_TOKEN_TYPES = SESSION_TOKEN_TYPES; +module.exports.DEVICE_MODELS_LIST = DEVICE_MODELS_LIST; module.exports.EVENT_LIST = EVENT_LIST; module.exports.LIFE_EVENT_LIST = LIFE_EVENT_LIST; @@ -455,7 +463,6 @@ module.exports.DEVICE_FEATURE_TYPES_LIST = DEVICE_FEATURE_TYPES_LIST; module.exports.USER_ROLE_LIST = USER_ROLE_LIST; module.exports.AVAILABLE_LANGUAGES_LIST = AVAILABLE_LANGUAGES_LIST; module.exports.SESSION_TOKEN_TYPE_LIST = SESSION_TOKEN_TYPE_LIST; -module.exports.DEVICE_MODELS_LIST = DEVICE_MODELS_LIST; module.exports.DEVICE_POLL_FREQUENCIES = DEVICE_POLL_FREQUENCIES; module.exports.DEVICE_POLL_FREQUENCIES_LIST = createList(DEVICE_POLL_FREQUENCIES); diff --git a/server/utils/coreErrors.js b/server/utils/coreErrors.js index 1b59d9933f..30204d144d 100644 --- a/server/utils/coreErrors.js +++ b/server/utils/coreErrors.js @@ -40,6 +40,13 @@ class BadParameters extends Error { } } +class AbortScene extends Error { + constructor(message) { + super(); + this.message = message; + } +} + module.exports = { PasswordNotMatchingError, NotFoundError, @@ -47,4 +54,5 @@ module.exports = { BadParameters, NoValuesFoundError, PlatformNotCompatible, + AbortScene, }; From 2f50bd57d3b030be05dd6b3da60229d0da6b35e0 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:33:22 +0100 Subject: [PATCH 062/236] mdoels up --- server/models/index.js | 4 --- server/models/scene.js | 72 ++++++++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/server/models/index.js b/server/models/index.js index 02a514e47a..2a00336b23 100644 --- a/server/models/index.js +++ b/server/models/index.js @@ -24,8 +24,6 @@ const SceneModel = require('./scene'); const ScriptModel = require('./script'); const ServiceModel = require('./service'); const SessionModel = require('./session'); -const TriggerSceneModel = require('./trigger_scene'); -const TriggerModel = require('./trigger'); const UserModel = require('./user'); const VariableModel = require('./variable'); @@ -48,8 +46,6 @@ const models = { Script: ScriptModel(sequelize, Sequelize), Service: ServiceModel(sequelize, Sequelize), Session: SessionModel(sequelize, Sequelize), - TriggerScene: TriggerSceneModel(sequelize, Sequelize), - Trigger: TriggerModel(sequelize, Sequelize), User: UserModel(sequelize, Sequelize), Variable: VariableModel(sequelize, Sequelize), }; diff --git a/server/models/scene.js b/server/models/scene.js index bc19a97ce6..bdcc254b23 100644 --- a/server/models/scene.js +++ b/server/models/scene.js @@ -1,5 +1,5 @@ -const Joi = require('joi'); -const { ACTION_LIST } = require('../utils/constants'); +const Joi = require('@hapi/joi').extend(require('@hapi/joi-date')); +const { ACTION_LIST, EVENT_LIST } = require('../utils/constants'); const { addSelector } = require('../utils/addSelector'); const iconList = require('../config/icons.json'); @@ -7,20 +7,52 @@ const actionSchema = Joi.array().items( Joi.array().items( Joi.object().keys({ type: Joi.string() - .valid(ACTION_LIST) + .valid(...ACTION_LIST) .required(), - deviceFeature: Joi.string(), + device_feature: Joi.string(), + device_features: Joi.array().items(Joi.string()), device: Joi.string(), + devices: Joi.array().items(Joi.string()), user: Joi.string(), text: Joi.string(), - milliseconds: Joi.number(), - seconds: Joi.number(), - minutes: Joi.number(), - hours: Joi.number(), + value: Joi.number(), + unit: Joi.string(), + conditions: Joi.array().items({ + variable: Joi.string().required(), + operator: Joi.string() + .valid('=', '!=', '>', '>=', '<', '<=') + .required(), + value: Joi.number(), + }), }), ), ); +const triggersSchema = Joi.array().items( + Joi.object().keys({ + type: Joi.string() + .valid(...EVENT_LIST) + .required(), + house: Joi.string(), + device: Joi.string(), + device_feature: Joi.string(), + operator: Joi.string().valid('=', '!=', '>', '>=', '<', '<='), + value: Joi.number(), + user: Joi.string(), + scheduler_type: Joi.string().valid('every-month', 'every-week', 'every-day', 'interval', 'custom-time'), + date: Joi.date().format('YYYY-MM-DD'), + time: Joi.string().regex(/^([0-9]{2}):([0-9]{2})$/), + interval: Joi.number(), + unit: Joi.string(), + days_of_the_week: Joi.array().items( + Joi.string().valid('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'), + ), + day_of_the_month: Joi.number() + .min(1) + .max(31), + }), +); + module.exports = (sequelize, DataTypes) => { const scene = sequelize.define( 't_scene', @@ -51,7 +83,18 @@ module.exports = (sequelize, DataTypes) => { type: DataTypes.JSON, validate: { isEven(value) { - const result = Joi.validate(value, actionSchema); + const result = actionSchema.validate(value); + if (result.error) { + throw new Error(result.error.details[0].message); + } + }, + }, + }, + triggers: { + type: DataTypes.JSON, + validate: { + isEven(value) { + const result = triggersSchema.validate(value); if (result.error) { throw new Error(result.error.details[0].message); } @@ -68,16 +111,5 @@ module.exports = (sequelize, DataTypes) => { // add slug if needed scene.beforeValidate(addSelector); - scene.associate = (models) => { - scene.belongsToMany(models.Trigger, { - through: { - model: models.TriggerScene, - unique: true, - }, - foreignKey: 'scene_id', - as: 'triggers', - }); - }; - return scene; }; From 54c7cda871b45c385e73c1daa4e4561daf0b52fd Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:34:17 +0100 Subject: [PATCH 063/236] migrations upd --- .../20200123094438-add-triggers-attribute.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 server/migrations/20200123094438-add-triggers-attribute.js diff --git a/server/migrations/20200123094438-add-triggers-attribute.js b/server/migrations/20200123094438-add-triggers-attribute.js new file mode 100644 index 0000000000..759aa85917 --- /dev/null +++ b/server/migrations/20200123094438-add-triggers-attribute.js @@ -0,0 +1,12 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('t_scene', 'triggers', { + type: Sequelize.JSON, + }); + // delete useless trigger table + await queryInterface.dropTable('t_trigger'); + // delete useless trigger_scene table + await queryInterface.dropTable('t_trigger_scene'); + }, + down: (queryInterface, Sequelize) => {}, +}; From a7d7ecd343dc903d6ba570e0b92c6bfec87914d3 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:35:32 +0100 Subject: [PATCH 064/236] api upd --- server/api/routes.js | 19 ------------------- server/api/websockets/index.js | 2 -- 2 files changed, 21 deletions(-) diff --git a/server/api/routes.js b/server/api/routes.js index 6ac6a505ee..4433cfe60d 100644 --- a/server/api/routes.js +++ b/server/api/routes.js @@ -15,7 +15,6 @@ const SessionController = require('./controllers/session.controller'); const ServiceController = require('./controllers/service.controller'); const SceneController = require('./controllers/scene.controller'); const SystemController = require('./controllers/system.controller'); -const TriggerController = require('./controllers/trigger.controller'); const VariableController = require('./controllers/variable.controller'); const WeatherController = require('./controllers/weather.controller'); @@ -45,7 +44,6 @@ function getRoutes(gladys) { const serviceController = ServiceController(gladys); const sceneController = SceneController(gladys); const systemController = SystemController(gladys); - const triggerController = TriggerController(gladys); const weatherController = WeatherController(gladys); const routes = {}; @@ -433,23 +431,6 @@ function getRoutes(gladys) { authenticated: true, controller: systemController.getUpgradeDownloadStatus, }, - // trigger - 'post /api/v1/trigger': { - authenticated: true, - controller: triggerController.create, - }, - 'get /api/v1/trigger': { - authenticated: true, - controller: triggerController.get, - }, - 'patch /api/v1/trigger/:trigger_selector': { - authenticated: true, - controller: triggerController.update, - }, - 'delete /api/v1/trigger/:trigger_selector': { - authenticated: true, - controller: triggerController.destroy, - }, // user 'post /api/v1/user': { authenticated: true, diff --git a/server/api/websockets/index.js b/server/api/websockets/index.js index 7f7c980d84..0e85cf8b26 100644 --- a/server/api/websockets/index.js +++ b/server/api/websockets/index.js @@ -92,7 +92,6 @@ function userDisconnected(user, client) { */ function init() { this.wss.on('connection', (ws) => { - logger.debug(`New user connected in websocket, ${ws}`); let user; let authenticated = false; ws.on('close', () => { @@ -114,7 +113,6 @@ function init() { authenticated = true; this.userConnected(user, ws); } catch (e) { - logger.debug(e); ws.close(4000, ERROR_MESSAGES.INVALID_ACCESS_TOKEN); } break; From c7905a580b89fd3c190913c007e66bdc2a81b325 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:36:29 +0100 Subject: [PATCH 065/236] seeders upd --- server/seeders/20190227043234-scene.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/seeders/20190227043234-scene.js b/server/seeders/20190227043234-scene.js index 759a88e85c..b55a369ba7 100644 --- a/server/seeders/20190227043234-scene.js +++ b/server/seeders/20190227043234-scene.js @@ -18,6 +18,7 @@ module.exports = { }, ], ]), + triggers: '[]', created_at: '2019-02-12 07:49:07.556 +00:00', updated_at: '2019-02-12 07:49:07.556 +00:00', }, From 524fefbc91d942b8823640a4a44ab860b89f1821 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:38:02 +0100 Subject: [PATCH 066/236] Add files via upload --- .../rflink/lib/commands/rflink.setValue.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js index 8d63d527cc..a0a7f1a2aa 100644 --- a/server/services/rflink/lib/commands/rflink.setValue.js +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -10,10 +10,8 @@ const logger = require('../../../../utils/logger'); * rflink.SetValue(); */ function setValue(device, deviceFeature, state) { - logger.log(deviceFeature); let msg; let value; - logger.log(device.external_id); value = state; @@ -60,15 +58,9 @@ function setValue(device, deviceFeature, state) { } logger.log(msg); - this.sendUsb.write(msg, (error) => { - logger.log(error); - }); - this.sendUsb.write(msg, (error) => { - logger.log(error); - }); - this.sendUsb.write(msg, (error) => { - logger.log(error); - }); + this.sendUsb.write(msg, (error) => {}); + this.sendUsb.write(msg, (error) => {}); + this.sendUsb.write(msg, (error) => {}); } module.exports = { From f18dc244d6ee54445546192574fea576dcc21e35 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:41:14 +0100 Subject: [PATCH 067/236] Add files via upload --- package.json | 111 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 6ba6f95acb..dd7eae564f 100644 --- a/package.json +++ b/package.json @@ -1,36 +1,99 @@ { - "name": "gladys", - "version": "4.0.0-beta", - "description": "An open-source home assistant", + "name": "gladys-server", + "description": "", "main": "index.js", "scripts": { - "postinstall": "run-p install-server:dev install-front:dev", - "start": "run-p start-server:dev start-front:dev", - "test": "run-p test-server test-front", - "install-server:dev": "cd server && npm install", - "start-server:dev": "cd server && npm start", - "test-server": "cd server && npm test", - "install-front:dev": "cd front && npm install", - "start-front:dev": "cd front && npm start", - "test-front": "cd front && npm test", - "db-migrate:dev": "cd server && npm run db-migrate:dev", - "build:clean": "shx --silent rm -rf server/static", - "build-front": "cd front && npm run build", - "copy-front": "shx cp -R front/build server/static", - "build": "npm run build:clean && npm run build-front && npm run copy-front", + "postinstall": "node ./cli/install_service_dependencies.js", + "clean:test": "shx --silent rm -f /tmp/gladys-test.db && shx echo Cleaned test database", + "pretest": "cross-env SQLITE_FILE_PATH=/tmp/gladys-test.db npm run db-migrate:test && npm run eslint", + "test": "cross-env SQLITE_FILE_PATH=/tmp/gladys-test.db NODE_ENV=test ./node_modules/mocha/bin/mocha --recursive ./test/bootstrap.test.js \"./test/**/*.test.js\" --exit", + "coverage": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov", "push-coverage": "codecov -F server", - "generate-changelog": "auto-changelog" - }, - "repository": { - "type": "git", - "url": "git@personal:GladysAssistant/gladys-4-playground.git" + "prettier-check": "prettier --check '**/*.js' '**/*.json'", + "prettier": "prettier --write '**/*.js' '**/*.json'", + "eslint-fix": "eslint . --fix", + "eslint": "eslint .", + "start": "cross-env NODE_ENV=development nodemon index.js", + "start:prod": "npm run db-migrate:prod && cross-env NODE_ENV=production node index.js", + "db-reset:dev": "cross-env NODE_ENV=development node_modules/.bin/sequelize db:migrate:undo:all", + "db-migrate:dev": "cross-env NODE_ENV=development node_modules/.bin/sequelize db:migrate", + "db-migrate:prod": "cross-env NODE_ENV=production node_modules/.bin/sequelize db:migrate", + "db-seed:dev": "cross-env NODE_ENV=development node_modules/.bin/sequelize db:seed:all", + "db-reset-seed:dev": "cross-env NODE_ENV=development node_modules/.bin/sequelize db:seed:undo:all", + "db-migrate:test": "cross-env NODE_ENV=test node_modules/.bin/sequelize db:migrate", + "jsdoc": "jsdoc lib/**/*.js -d jsdoc", + "apidoc": "apidoc -i controllers/ -o apidoc/", + "checkjs": "tsc --allowJs --resolveJsonModule --checkJs --noEmit --lib ES5,ES2018 lib/**/*.js api/**/*.js services/**/*.js", + "generate-apidoc": "apidoc -i api/ -o apidoc", + "generate-jsdoc": "jsdoc lib/**/*.js services/**/*.js -d jsdoc" }, "author": "Pierre-Gilles Leymarie", "license": "MIT", + "nodemonConfig": { + "ignore": [ + "test/*" + ] + }, + "nyc": { + "exclude": [ + "migrations/**/*.js", + "test/**/*.js" + ] + }, "devDependencies": { + "apidoc": "^0.17.7", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "codecov": "^3.5.0", + "dotenv": "^6.2.0", + "eslint": "^5.13.0", + "eslint-config-airbnb": "^17.1.0", + "eslint-config-prettier": "^4.3.0", + "eslint-plugin-import": "^2.16.0", + "eslint-plugin-jsdoc": "^4.1.0", + "eslint-plugin-require-jsdoc": "^1.0.4", + "jsdoc": "^3.5.5", + "mocha": "^5.2.0", + "mock-express-request": "^0.2.2", + "nock": "^10.0.6", + "nodemon": "^1.19.0", "npm-run-all": "^4.1.5", - "shx": "^0.3.2" + "nyc": "^13.3.0", + "prettier": "^1.17.1", + "proxyquire": "^2.1.0", + "shx": "^0.3.2", + "sinon": "^7.2.4", + "supertest": "^3.4.2" }, - "dependencies": {} + "dependencies": { + "@gladysassistant/gladys-gateway-js": "^3.2.2", + "@hapi/joi": "^17.1.0", + "@hapi/joi-date": "^2.0.1", + "bcrypt": "^3.0.3", + "bluebird": "^3.5.3", + "compression": "^1.7.4", + "cross-env": "^5.2.0", + "dockerode": "^2.5.8", + "express": "^4.16.4", + "express-rate-limit": "^4.0.3", + "form-data": "^2.3.3", + "fs-extra": "^8.0.1", + "get-value": "^3.0.1", + "handlebars": "^4.1.0", + "joi": "^14.3.1", + "jsonwebtoken": "^8.4.0", + "lodash.clonedeep": "^4.5.0", + "node-nlp": "^3.0.3", + "node-schedule": "^1.3.2", + "node-webcrypto-ossl": "^1.0.48", + "path-to-regexp": "^3.0.0", + "queue": "^6.0.0", + "semver": "^6.1.1", + "sequelize": "^4.42.0", + "sequelize-cli": "^5.5.1", + "sqlite3": "^4.0.6", + "tracer": "^0.9.8", + "uuid": "^3.3.2", + "ws": "^6.2.1" + } } From 869856f40d85ce9b50bb0b70f0d5a9510fa2d92b Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:43:49 +0100 Subject: [PATCH 068/236] Add files via upload --- server/package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index b75594bda2..dd7eae564f 100644 --- a/server/package.json +++ b/server/package.json @@ -67,6 +67,8 @@ }, "dependencies": { "@gladysassistant/gladys-gateway-js": "^3.2.2", + "@hapi/joi": "^17.1.0", + "@hapi/joi-date": "^2.0.1", "bcrypt": "^3.0.3", "bluebird": "^3.5.3", "compression": "^1.7.4", @@ -80,13 +82,15 @@ "handlebars": "^4.1.0", "joi": "^14.3.1", "jsonwebtoken": "^8.4.0", + "lodash.clonedeep": "^4.5.0", "node-nlp": "^3.0.3", + "node-schedule": "^1.3.2", "node-webcrypto-ossl": "^1.0.48", "path-to-regexp": "^3.0.0", "queue": "^6.0.0", "semver": "^6.1.1", "sequelize": "^4.42.0", - "sequelize-cli": "^5.4.0", + "sequelize-cli": "^5.5.1", "sqlite3": "^4.0.6", "tracer": "^0.9.8", "uuid": "^3.3.2", From beb507393beaba9ccd31ae0518a4f3ac707abf30 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:46:42 +0100 Subject: [PATCH 069/236] scene lib update fix --- server/lib/scene/index.js | 12 ++- server/lib/scene/scene.actions.js | 88 ++++++++++++++---- server/lib/scene/scene.addScene.js | 89 ++++++++++++++++++- server/lib/scene/scene.cancelTriggers.js | 24 +++++ server/lib/scene/scene.checkTrigger.js | 46 ++++++++++ server/lib/scene/scene.destroy.js | 4 +- server/lib/scene/scene.execute.js | 8 +- server/lib/scene/scene.executeActions.js | 14 ++- server/lib/scene/scene.executeSingleAction.js | 5 +- server/lib/scene/scene.get.js | 6 +- server/lib/scene/scene.triggers.js | 18 ++++ 11 files changed, 287 insertions(+), 27 deletions(-) create mode 100644 server/lib/scene/scene.cancelTriggers.js create mode 100644 server/lib/scene/scene.checkTrigger.js create mode 100644 server/lib/scene/scene.triggers.js diff --git a/server/lib/scene/index.js b/server/lib/scene/index.js index d5a538e7f2..6d924bf8d0 100644 --- a/server/lib/scene/index.js +++ b/server/lib/scene/index.js @@ -1,7 +1,9 @@ const queue = require('queue'); const { addScene } = require('./scene.addScene'); const { create } = require('./scene.create'); +const { checkTrigger } = require('./scene.checkTrigger'); const { init } = require('./scene.init'); +const { cancelTriggers } = require('./scene.cancelTriggers'); const { destroy } = require('./scene.destroy'); const { execute } = require('./scene.execute'); const { get } = require('./scene.get'); @@ -9,20 +11,28 @@ const { getBySelector } = require('./scene.getBySelector'); const { executeSingleAction } = require('./scene.executeSingleAction'); const { update } = require('./scene.update'); -const SceneManager = function SceneManager(stateManager, event, device) { +const { EVENTS } = require('../../utils/constants'); +const { eventFunctionWrapper } = require('../../utils/functionsWrapper'); + +const SceneManager = function SceneManager(stateManager, event, device, message) { this.stateManager = stateManager; this.event = event; this.device = device; + this.message = message; this.scenes = {}; // @ts-ignore this.queue = queue({ autostart: true, concurrency: 1, }); + this.event.on(EVENTS.TRIGGERS.CHECK, eventFunctionWrapper(this.checkTrigger.bind(this))); + this.event.on(EVENTS.ACTION.TRIGGERED, eventFunctionWrapper(this.executeSingleAction.bind(this))); }; SceneManager.prototype.addScene = addScene; +SceneManager.prototype.cancelTriggers = cancelTriggers; SceneManager.prototype.create = create; +SceneManager.prototype.checkTrigger = checkTrigger; SceneManager.prototype.destroy = destroy; SceneManager.prototype.get = get; SceneManager.prototype.init = init; diff --git a/server/lib/scene/scene.actions.js b/server/lib/scene/scene.actions.js index 728e84b154..eb64ec7bc9 100644 --- a/server/lib/scene/scene.actions.js +++ b/server/lib/scene/scene.actions.js @@ -1,9 +1,12 @@ -const { ACTIONS } = require('../../utils/constants'); +const Promise = require('bluebird'); +const { ACTIONS, DEVICE_FEATURE_CATEGORIES, DEVICE_FEATURE_TYPES } = require('../../utils/constants'); const { getDeviceFeature } = require('../../utils/device'); +const { AbortScene } = require('../../utils/coreErrors'); +const { compare } = require('../../utils/compare'); const logger = require('../../utils/logger'); const actionsFunc = { - [ACTIONS.DEVICE.SET_VALUE]: async (self, action, scope) => { + [ACTIONS.DEVICE.SET_VALUE]: async (self, action, scope, columnIndex, rowIndex) => { let device; let deviceFeature; if (action.device_feature) { @@ -16,28 +19,81 @@ const actionsFunc = { return self.device.setValue(device, deviceFeature, action.value); }, [ACTIONS.LIGHT.TURN_ON]: async (self, action, scope) => { - const light = self.stateManager.get('device', action.device); - return light.turnOn(); + await Promise.map(action.devices, async (deviceSelector) => { + try { + const device = self.stateManager.get('device', deviceSelector); + const deviceFeature = getDeviceFeature( + device, + DEVICE_FEATURE_CATEGORIES.LIGHT, + DEVICE_FEATURE_TYPES.LIGHT.BINARY, + ); + await self.device.setValue(device, deviceFeature, 1); + } catch (e) { + logger.warn(e); + } + }); + }, + [ACTIONS.LIGHT.TURN_OFF]: async (self, action, scope) => { + await Promise.map(action.devices, async (deviceSelector) => { + try { + const device = self.stateManager.get('device', deviceSelector); + const deviceFeature = getDeviceFeature( + device, + DEVICE_FEATURE_CATEGORIES.LIGHT, + DEVICE_FEATURE_TYPES.LIGHT.BINARY, + ); + await self.device.setValue(device, deviceFeature, 0); + } catch (e) { + logger.warn(e); + } + }); }, [ACTIONS.TIME.DELAY]: async (self, action, scope) => new Promise((resolve) => { - if (action.milliseconds) { - setTimeout(resolve, action.milliseconds); - } else if (action.seconds) { - logger.debug(`Waiting ${action.seconds} seconds...`); - setTimeout(resolve, action.seconds * 1000); - } else if (action.minutes) { - setTimeout(resolve, action.minutes * 1000 * 60); - } else if (action.hours) { - setTimeout(resolve, action.hours * 1000 * 60 * 60); + let timeToWaitMilliseconds; + switch (action.unit) { + case 'milliseconds': + timeToWaitMilliseconds = action.value; + break; + case 'seconds': + timeToWaitMilliseconds = action.value * 1000; + break; + case 'minutes': + timeToWaitMilliseconds = action.value * 1000 * 60; + break; + case 'hours': + timeToWaitMilliseconds = action.value * 1000 * 60 * 60; + break; + default: + throw new Error(`Unit ${action.unit} not recognized`); } + setTimeout(resolve, timeToWaitMilliseconds); }), [ACTIONS.SERVICE.START]: async (self, action, scope) => self.stateManager.get('service', action.service).start(), [ACTIONS.SERVICE.STOP]: async (self, action, scope) => self.stateManager.get('service', action.service).stop(), [ACTIONS.SCENE.START]: async (self, action, scope) => self.execute(action.scene, scope), - [ACTIONS.TELEGRAM.SEND]: async (self, action, scope) => { - const user = self.stateManager.get('user', action.user); - await self.stateManager.get('service', 'telegram').message.send(user.telegram_user_id, action.text); + [ACTIONS.MESSAGE.SEND]: async (self, action, scope) => { + await self.message.sendToUser(action.user, action.text); + }, + [ACTIONS.DEVICE.GET_VALUE]: async (self, action, scope, columnIndex, rowIndex) => { + const deviceFeature = self.stateManager.get('deviceFeature', action.device_feature); + scope[`${columnIndex}.${rowIndex}.last_value`] = deviceFeature.last_value; + }, + [ACTIONS.CONDITION.ONLY_CONTINUE_IF]: async (self, action, scope) => { + let oneConditionVerified = false; + action.conditions.forEach((condition) => { + const conditionVerified = compare(condition.operator, scope[condition.variable], condition.value); + if (conditionVerified) { + oneConditionVerified = true; + } else { + logger.debug( + `Condition not verified. Condition = ${scope[condition.variable]} ${condition.operator} ${condition.value}`, + ); + } + }); + if (oneConditionVerified === false) { + throw new AbortScene('CONDITION_NOT_VERIFIED'); + } }, }; diff --git a/server/lib/scene/scene.addScene.js b/server/lib/scene/scene.addScene.js index 3d3d9fcefc..d52e65ac57 100644 --- a/server/lib/scene/scene.addScene.js +++ b/server/lib/scene/scene.addScene.js @@ -1,13 +1,98 @@ +const schedule = require('node-schedule'); +const cloneDeep = require('lodash.clonedeep'); +const uuid = require('uuid'); + +const { BadParameters } = require('../../utils/coreErrors'); +const { EVENTS } = require('../../utils/constants'); + +const MAX_VALUE_SET_INTERVAL = 2 ** 31 - 1; + +const nodeScheduleDaysOfWeek = { + sunday: 0, + monday: 1, + tuesday: 2, + wednesday: 3, + thursday: 4, + friday: 5, + saturday: 6, +}; + /** * @description Add a scene to the scene manager. - * @param {Object} scene - Scene object from DB. + * @param {Object} sceneRaw - Scene object from DB. + * @returns {Object} Return the scene. * @example * addScene({ * selector: 'test' * }); */ -async function addScene(scene) { +function addScene(sceneRaw) { + // deep clone the scene so that we don't modify the same object which will be returned to the client + const scene = cloneDeep(sceneRaw); + // first, if the scene actually exist, we cancel all triggers + this.cancelTriggers(scene.selector); + // Foreach triggger, we schedule jobs for triggers that need to be scheduled + if (scene.triggers) { + scene.triggers.forEach((trigger) => { + // First, we had a trigger key, import to uniquely identify this trigger + trigger.key = uuid.v4(); + if (trigger.type === EVENTS.TIME.CHANGED && trigger.scheduler_type !== 'interval') { + const rule = new schedule.RecurrenceRule(); + switch (trigger.scheduler_type) { + case 'every-month': + rule.date = trigger.day_of_the_month; + rule.hour = parseInt(trigger.time.substr(0, 2), 10); + rule.minute = parseInt(trigger.time.substr(3, 2), 10); + rule.second = 0; + break; + case 'every-week': + rule.dayOfWeek = trigger.days_of_the_week.map((day) => nodeScheduleDaysOfWeek[day]); + rule.hour = parseInt(trigger.time.substr(0, 2), 10); + rule.minute = parseInt(trigger.time.substr(3, 2), 10); + rule.second = 0; + break; + case 'every-day': + rule.hour = parseInt(trigger.time.substr(0, 2), 10); + rule.minute = parseInt(trigger.time.substr(3, 2), 10); + rule.second = 0; + break; + case 'custom-time': + rule.year = parseInt(trigger.date.substr(0, 4), 10); + rule.month = parseInt(trigger.date.substr(5, 2), 10) - 1; + rule.date = parseInt(trigger.date.substr(8, 4), 10); + rule.hour = parseInt(trigger.time.substr(0, 2), 10); + rule.minute = parseInt(trigger.time.substr(3, 2), 10); + rule.second = 0; + break; + default: + throw new BadParameters(`${trigger.scheduler_type} not supported`); + } + trigger.nodeScheduleJob = schedule.scheduleJob(rule, () => this.event.emit(EVENTS.TRIGGERS.CHECK, trigger)); + } else if (trigger.type === EVENTS.TIME.CHANGED && trigger.scheduler_type === 'interval') { + let intervalMilliseconds; + switch (trigger.unit) { + case 'second': + intervalMilliseconds = trigger.interval * 1000; + break; + case 'minute': + intervalMilliseconds = trigger.interval * 60 * 1000; + break; + case 'hour': + intervalMilliseconds = trigger.interval * 60 * 60 * 1000; + break; + default: + throw new BadParameters(`${trigger.unit} not supported`); + } + if (intervalMilliseconds > MAX_VALUE_SET_INTERVAL) { + throw new BadParameters(`${trigger.interval} ${trigger.unit} is too big for an interval`); + } + trigger.jsInterval = setInterval(() => this.event.emit(EVENTS.TRIGGERS.CHECK, trigger), intervalMilliseconds); + } + }); + } + this.scenes[scene.selector] = scene; + return scene; } module.exports = { diff --git a/server/lib/scene/scene.cancelTriggers.js b/server/lib/scene/scene.cancelTriggers.js new file mode 100644 index 0000000000..1699719ba1 --- /dev/null +++ b/server/lib/scene/scene.cancelTriggers.js @@ -0,0 +1,24 @@ +/** + * @description Cancel a trigger. + * @param {Object} sceneSelector - The selector of the scene to clean. + * @example + * this.cancelTriggers('test-scene'); + */ +function cancelTriggers(sceneSelector) { + if (this.scenes[sceneSelector] && this.scenes[sceneSelector].triggers) { + this.scenes[sceneSelector].triggers.forEach((trigger) => { + if (trigger.nodeScheduleJob) { + trigger.nodeScheduleJob.cancel(); + delete trigger.nodeScheduleJob; + } + if (trigger.jsInterval) { + clearInterval(trigger.jsInterval); + delete trigger.jsInterval; + } + }); + } +} + +module.exports = { + cancelTriggers, +}; diff --git a/server/lib/scene/scene.checkTrigger.js b/server/lib/scene/scene.checkTrigger.js new file mode 100644 index 0000000000..c4cfd5935e --- /dev/null +++ b/server/lib/scene/scene.checkTrigger.js @@ -0,0 +1,46 @@ +const { triggersFunc } = require('./scene.triggers'); +const logger = require('../../utils/logger'); + +/** + * @description checkTrigger verify if the current event verify + * a trigger. + * @param {Object} event - The event to check. + * @example + * checkTrigger({ type: 'device.new-state' }) + */ +function checkTrigger(event) { + logger.debug(`Trigger: new event checkTrigger "${event.type}"`); + if (!triggersFunc[event.type]) { + throw new Error(`Trigger type "${event.type}" has no checker function.`); + } + const sceneSelectors = Object.keys(this.scenes); + + // foreach scenes we have in RAM + sceneSelectors.forEach((sceneSelector) => { + // we check if the scene has triggers + if (this.scenes[sceneSelector].triggers && this.scenes[sceneSelector].triggers instanceof Array) { + // if yes, we loop on each trigger + this.scenes[sceneSelector].triggers.forEach((trigger) => { + logger.debug(`Checking trigger ${trigger.type}...`); + // we check that trigger type is matching the event + if (event.type === trigger.type) { + logger.debug(`Trigger ${trigger.type} is matching with event`); + // then we check the condition is verified + const conditionVerified = triggersFunc[event.type](event, trigger); + logger.debug(`Trigger ${trigger.type}, conditionVerified = ${conditionVerified}...`); + + // if yes, we execute the scene + if (conditionVerified) { + this.execute(sceneSelector, { + triggerEvent: event, + }); + } + } + }); + } + }); +} + +module.exports = { + checkTrigger, +}; diff --git a/server/lib/scene/scene.destroy.js b/server/lib/scene/scene.destroy.js index e703a279db..e5da4e119e 100644 --- a/server/lib/scene/scene.destroy.js +++ b/server/lib/scene/scene.destroy.js @@ -19,7 +19,9 @@ async function destroy(selector) { } await existingScene.destroy(); - + // we cancel triggers linked to the scene + this.cancelTriggers(selector); + // then we delete the scene in RAM delete this.scenes[selector]; } diff --git a/server/lib/scene/scene.execute.js b/server/lib/scene/scene.execute.js index 34ec9a044f..91212cdefe 100644 --- a/server/lib/scene/scene.execute.js +++ b/server/lib/scene/scene.execute.js @@ -13,7 +13,13 @@ function execute(sceneSelector, scope) { if (!this.scenes[sceneSelector]) { throw new Error(`Scene with selector ${sceneSelector} not found.`); } - this.queue.push(() => executeActions(this, this.scenes[sceneSelector].actions, scope)); + this.queue.push(async () => { + try { + await executeActions(this, this.scenes[sceneSelector].actions, scope); + } catch (e) { + logger.error(e); + } + }); } catch (e) { logger.error(e); } diff --git a/server/lib/scene/scene.executeActions.js b/server/lib/scene/scene.executeActions.js index dfeae17df6..138e9bd503 100644 --- a/server/lib/scene/scene.executeActions.js +++ b/server/lib/scene/scene.executeActions.js @@ -1,6 +1,8 @@ const Promise = require('bluebird'); const { actionsFunc } = require('./scene.actions'); const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../utils/constants'); +const { AbortScene } = require('../../utils/coreErrors'); +const logger = require('../../utils/logger'); /** * @description Execute one action. @@ -25,8 +27,16 @@ async function executeAction(self, action, scope, columnIndex, rowIndex) { payload: { columnIndex, rowIndex }, }); } - // execute action - await actionsFunc[action.type](self, action, scope); + try { + // execute action + await actionsFunc[action.type](self, action, scope, columnIndex, rowIndex); + } catch (e) { + if (e instanceof AbortScene) { + throw e; + } else { + logger.warn(e); + } + } // send message to tell the UI the action has finished being executed if (columnIndex !== undefined && rowIndex !== undefined) { diff --git a/server/lib/scene/scene.executeSingleAction.js b/server/lib/scene/scene.executeSingleAction.js index a801f1f698..f9490c6db5 100644 --- a/server/lib/scene/scene.executeSingleAction.js +++ b/server/lib/scene/scene.executeSingleAction.js @@ -4,16 +4,17 @@ const { executeAction } = require('./scene.executeActions'); /** * @description Execute an action coming from an event. * @param {Object} action - The action to execute. + * @param {Object} scope - The scope of the action. * @example * scene.executeSingleAction({ * type: 'light.turn-on', * device: 'light', * }); */ -async function executeSingleAction(action) { +async function executeSingleAction(action, scope = {}) { logger.debug(`Executing action of type ${action.type}`); try { - await executeAction(this, action, action.scope); + await executeAction(this, action, scope); } catch (e) { logger.warn(`There was an error executing action ${action.type}`); logger.warn(e); diff --git a/server/lib/scene/scene.get.js b/server/lib/scene/scene.get.js index 6dc0a21414..d9770b251c 100644 --- a/server/lib/scene/scene.get.js +++ b/server/lib/scene/scene.get.js @@ -4,7 +4,6 @@ const db = require('../../models'); const DEFAULT_OPTIONS = { fields: ['id', 'name', 'icon', 'selector', 'last_executed', 'updated_at'], - take: 20, skip: 0, order_dir: 'ASC', order_by: 'name', @@ -24,11 +23,14 @@ async function get(options) { const queryParams = { attributes: optionsWithDefault.fields, - limit: optionsWithDefault.take, offset: optionsWithDefault.skip, order: [[optionsWithDefault.order_by, optionsWithDefault.order_dir]], }; + if (optionsWithDefault.take !== undefined) { + queryParams.limit = optionsWithDefault.take; + } + if (optionsWithDefault.search) { queryParams.where = Sequelize.where(Sequelize.fn('lower', Sequelize.col('name')), { [Op.like]: `%${optionsWithDefault.search}%`, diff --git a/server/lib/scene/scene.triggers.js b/server/lib/scene/scene.triggers.js new file mode 100644 index 0000000000..8ac4a8bb41 --- /dev/null +++ b/server/lib/scene/scene.triggers.js @@ -0,0 +1,18 @@ +const { EVENTS } = require('../../utils/constants'); +const { compare } = require('../../utils/compare'); + +const triggersFunc = { + [EVENTS.DEVICE.NEW_STATE]: (event, trigger) => { + // we check that we are talking about the same event + if (event.device_feature !== trigger.device_feature) { + return false; + } + // we compare the value with the expected value + return compare(trigger.operator, event.last_value, trigger.value); + }, + [EVENTS.TIME.CHANGED]: (event, trigger) => event.key === trigger.key, +}; + +module.exports = { + triggersFunc, +}; From 0a5316ceef6589d2f04a69c3d7b0f6bdbe5057b7 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Sat, 21 Mar 2020 12:56:25 +0100 Subject: [PATCH 070/236] tests fixes --- .../services/rflink/lib/devicesToTest.test.js | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/server/test/services/rflink/lib/devicesToTest.test.js b/server/test/services/rflink/lib/devicesToTest.test.js index 7bc21a448f..7d74f06850 100644 --- a/server/test/services/rflink/lib/devicesToTest.test.js +++ b/server/test/services/rflink/lib/devicesToTest.test.js @@ -24,7 +24,29 @@ const DEVICES = [ }, ], }, - {}, + { + service_id: 'a810b8db-6d04-4697-bed3-c4b72c996279', + name: `Prise `, + selector: `rflink:86aa70:10`, + external_id: `rflink:86aa70:10`, + model: 'Tristate', + should_poll: false, + features: [ + { + name: 'switch', + selector: `rflink:86aa70:switch:10`, + external_id: `rflink:86aa70:switch:10`, + rfcode: 'CMD', + category: DEVICE_FEATURE_CATEGORIES.SWITCH, + type: DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only: false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + }, + ], + }, ]; module.exports = DEVICES; From a497f019c3889067f83166a3485fc942a28452b3 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 6 Feb 2020 20:10:02 +0100 Subject: [PATCH 071/236] create a device when a message is received --- .../services/rflink/api/rflink.controller.js | 72 ++++ .../rflink/api/rflink.parse.ObjToRF.js | 25 ++ .../rflink/api/rflink.parse.RFtoObject.js | 45 +++ server/services/rflink/index.js | 58 +++ .../rflink/lib/commands/rflink.connect.js | 26 ++ .../rflink/lib/commands/rflink.disconnect.js | 20 + .../rflink/lib/commands/rflink.getDevices.js | 16 + .../rflink/lib/commands/rflink.listen.js | 18 + .../rflink/lib/commands/rflink.setValue.js | 26 ++ .../rflink/lib/events/rflink.addDevice.js | 38 ++ .../rflink/lib/events/rflink.connected.js | 20 + .../rflink/lib/events/rflink.error.js | 20 + .../rflink/lib/events/rflink.message.js | 372 ++++++++++++++++++ .../rflink/lib/events/rflink.newValue.js | 24 ++ server/services/rflink/lib/index.js | 62 +++ server/services/rflink/package.json | 21 + 16 files changed, 863 insertions(+) create mode 100644 server/services/rflink/api/rflink.controller.js create mode 100644 server/services/rflink/api/rflink.parse.ObjToRF.js create mode 100644 server/services/rflink/api/rflink.parse.RFtoObject.js create mode 100644 server/services/rflink/index.js create mode 100644 server/services/rflink/lib/commands/rflink.connect.js create mode 100644 server/services/rflink/lib/commands/rflink.disconnect.js create mode 100644 server/services/rflink/lib/commands/rflink.getDevices.js create mode 100644 server/services/rflink/lib/commands/rflink.listen.js create mode 100644 server/services/rflink/lib/commands/rflink.setValue.js create mode 100644 server/services/rflink/lib/events/rflink.addDevice.js create mode 100644 server/services/rflink/lib/events/rflink.connected.js create mode 100644 server/services/rflink/lib/events/rflink.error.js create mode 100644 server/services/rflink/lib/events/rflink.message.js create mode 100644 server/services/rflink/lib/events/rflink.newValue.js create mode 100644 server/services/rflink/lib/index.js create mode 100644 server/services/rflink/package.json diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js new file mode 100644 index 0000000000..e59aee8b58 --- /dev/null +++ b/server/services/rflink/api/rflink.controller.js @@ -0,0 +1,72 @@ +const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); +const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); +const logger = require('../../../utils/logger'); + +module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { + /** + * @api {get} /api/v1/service/rflink/devices get rflink devices + * @apiName getDevices + * @apiGroup RFlink + */ + async function getDevices(req, res) { + logger.log('getting devices ...'); + res.json(RFlinkManager.getDevices()); + } + + /** + * @api {get} /api/v1/service/rflink/connect connect to the gateway + * @apiName connect + * @apiGroup RFlink + */ + async function connect(req, res) { + const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); + if (!rflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } + RFlinkManager.connect(rflinkPath); + res.json({succes: true, }); + } + /** + * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway + * @apiName disconnect + * @apiGroup RFlink + */ + async function disconnect(req, res) { + RFlinkManager.disconnect(); + res.json({ + success: true, + }); + } + /** + * @api {get} /api/v1/service/rflink/status get the gateway's status + * @apiName getStatus + * @apiGroup RFlink + */ + async function getStatus(req, res) { + logger.log('getting status'); + res.json({ + connected: RFlinkManager.connected, + scanInProgress: RFlinkManager.scanInProgress, + }); + } + + return { + 'get /api/v1/service/rflink/devices' : { + authenticated: true, + controller: asyncMiddleware(getDevices) + }, + 'post /api/v1/service/rflink/connect' : { + authenticated: true, + controller: asyncMiddleware(connect) + }, + 'post /api/v1/service/rflink/disconnect' : { + authenticated: true, + controller: asyncMiddleware(disconnect) + }, + 'get /api/v1/sevice/rflink/status' : { + authenticated: true, + controller: asyncMiddleware(getStatus) + } + + }; +}; \ No newline at end of file diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js new file mode 100644 index 0000000000..f01c8f32b2 --- /dev/null +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -0,0 +1,25 @@ + +// eslint-disable-next-line jsdoc/check-alignment +/** +* @description convert a rflink device object to a string that can be sent to rflink +* @param {Object} device - Secure node. +* @example +* rflink.ObjToRF(device); +*/ +function ObjToRF(device) { + const id = device.external_id.slit(':')[1]; + + let Rfcode = `10;${device.model};${id};`; + + for (let i = 0;i { + this.message(data); + + + + }); +} + +module.exports = { + listen, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/commands/rflink.setValue.js b/server/services/rflink/lib/commands/rflink.setValue.js new file mode 100644 index 0000000000..08ece16789 --- /dev/null +++ b/server/services/rflink/lib/commands/rflink.setValue.js @@ -0,0 +1,26 @@ +const ObjToRF = require('../../api/rflink.parse.ObjToRF'); +const logger = require('../../../../utils/logger'); +const { EVENTS } = require('../../../../utils/constants'); +/** + * @description send a message to change a device's value + * @param {Object} device - The device to control. + * @param {Object} deviceFeature - The feature to control. + * @param {Object} state - The new state. + * @example + * rflink.SetValue(2a11d,{name : action , value : ON}); + */ +function SetValue(device, deviceFeature, state) { + this.newValue(device, deviceFeature, state); + + + + const msg = ObjToRF.ObjToRF(device); + this.usb.write(msg); + + +} + + +module.exports = { + SetValue, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.addDevice.js b/server/services/rflink/lib/events/rflink.addDevice.js new file mode 100644 index 0000000000..e8b29e75f6 --- /dev/null +++ b/server/services/rflink/lib/events/rflink.addDevice.js @@ -0,0 +1,38 @@ +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-restricted-syntax */ +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + + +/** + * @description Add device. + * @param {Object} device - Device to add. + * @example + * Rflink.addDevice(device); + */ +function addDevice(device) { + const id = device.external_id.split(':')[1]; + + + this.gladys.event.emit(EVENTS.DEVICE.NEW, + device, + ); + + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, + payload : device, + }); + + this.device[id] = device; + + + + + + + + + + +} + +module.exports = {addDevice}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.connected.js b/server/services/rflink/lib/events/rflink.connected.js new file mode 100644 index 0000000000..9770f9359f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.connected.js @@ -0,0 +1,20 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When the gateway is connected + * @example + * rflink.on('connected', this.connected); + */ +function connected() { + logger.debug(`Rflink : Gateway is connected`); + this.connected = true; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, + payload: {}, + }); +} + +module.exports = { + connected, +}; diff --git a/server/services/rflink/lib/events/rflink.error.js b/server/services/rflink/lib/events/rflink.error.js new file mode 100644 index 0000000000..52f363e50f --- /dev/null +++ b/server/services/rflink/lib/events/rflink.error.js @@ -0,0 +1,20 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When an error occur + * @example + * rflink.on('failed', this.driverFailed); + */ +function failed() { + logger.debug(`RFlink: Failed to start`); + this.connected = false; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.ERROR, + payload: {}, + }); +} + +module.exports = { + failed, +}; diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js new file mode 100644 index 0000000000..5fe1934c68 --- /dev/null +++ b/server/services/rflink/lib/events/rflink.message.js @@ -0,0 +1,372 @@ +const logger = require('../../../../utils/logger'); +const RFtoObj = require('../../api/rflink.parse.RFtoObject'); +const { + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES, + DEVICE_FEATURE_UNITS, + } = require('../../../../utils/constants'); + + + +/** + * @description when a message is received by the rflink gateway + * @param {string} msgRF - The message. + * @example + * rflink.message(msg); + */ +function message(msgRF) { + + const msg = RFtoObj(msgRF); + let newDevice; + + if (typeof msg.id === 'string'){ + if (msg.id.includes('=') === false) { + + const doesntExistYet = this.device[msg.id] === undefined; + + if (doesntExistYet === true) { + + + newDevice = { + service_id : this.serviceId, + name : `${msg.protocol} `, + selector : `rflink:${msg.id}`, + external_id: `rflink:${msg.id}`, + model : `${msg.protocol}`, + features : [] + }; + + + if (msg.temp !== undefined) { + newDevice.name += 'temperature sensor'; + newDevice.features.push({ + name : 'temperature', + selector : `rflink:${msg.id}:temperature`, + external_id : `rflink:${msg.id}:temperature`, + rfcode : 'TEMP', + category : DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.CELSIUS, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.hum !== undefined) { + newDevice.features.push({ + name : 'humidity', + selector : `rflink:${msg.id}:humidity`, + external_id : `rflink:${msg.id}:humidity`, + rfcode : 'HUM', + category : DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.baro !== undefined) { + newDevice.name += 'pressure sensor'; + newDevice.features.push({ + name : 'pressure', + selector : `rflink:${msg.id}:pressure`, + external_id : `rflink:${msg.id}:pressure`, + rfcode : 'BARO', + category : DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PASCAL, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100000000, + + + }); + } + if (msg.uv !== undefined) { + newDevice.name += 'uv sensor'; + newDevice.features.push({ + name : 'uv intensity', + selector : `rflink:${msg.id}:uv`, + external_id : `rflink:${msg.id}:uv`, + rfcode : 'UV', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.lux !== undefined) { + newDevice.name += 'light sensor'; + newDevice.features.push({ + name : 'light intensity', + selector : `rflink:${msg.id}:light-intensity`, + external_id : `rflink:${msg.id}:light-intensity`, + rfcode : 'LUX', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.LUX, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.bat !== undefined) { + newDevice.features.push({ + name : 'battery', + selector : `rflink:${msg.id}:battery`, + external_id : `rflink:${msg.id}:battery`, + rfcode : 'BAT', + category : DEVICE_FEATURE_CATEGORIES.BATTERY, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.rain !== undefined || msg.rainrate !== undefined) { + newDevice.name += 'rain sensor'; + newDevice.features.push({ + name : 'rain', + selector : `rflink:${msg.id}:rain`, + external_id : `rflink:${msg.id}:rain`, + rfcode : 'RAIN', + category : DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { + newDevice.name += 'wind speed sensor'; + newDevice.features.push({ + name : 'wind speed', + selector : `rflink:${msg.id}:wind-speed`, + external_id : `rflink:${msg.id}:wind-speed`, + rfcode : 'WINSP', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 500, + + + }); + } + if (msg.windir !== undefined) { + newDevice.name += 'wind direction sensor'; + newDevice.features.push({ + name : 'wind direction', + selector : `rflink:${msg.id}:wind-dir`, + external_id : `rflink:${msg.id}:wind-dir`, + rfcode : 'WINDIR', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.co2 !== undefined) { + newDevice.name += 'co2 sensor'; + newDevice.features.push({ + name : 'co2', + selector : `rflink:${msg.id}:co2`, + external_id : `rflink:${msg.id}:co2`, + rfcode : 'CO2', + category : DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + newDevice.name += 'switch'; + newDevice.features.push({ + name : 'switch', + selector : `rflink:${msg.id}:switch`, + external_id : `rflink:${msg.id}:switch`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.SWITCH, + type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only : false, + keep_history: true, + has_feedback: false, + min: 'OFF', + max: 'ON', + + + }); + } + + + if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { + newDevice.features.push({ + name : 'color', + selector : `rflink:${msg.id}:color`, + external_id : `rflink:${msg.id}:color`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 255, + + + }); + newDevice.features.push({ + name : 'brightness', + selector : `rflink:${msg.id}:brightness`, + external_id : `rflink:${msg.id}:brightness`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + newDevice.features.push({ + name : 'milight-mode', + selector : `rflink:${msg.id}:milight-mode`, + external_id : `rflink:${msg.id}:milight-mode`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.MODE, + read_only : false, + keep_history: true, + has_feedback: false, + min: 1, + max: 8, + + + }); + + + } + + + this.addDevice(newDevice); + + } else if (doesntExistYet === false) { + + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.hum !== undefined) { + this.newValue(msg, 'humidity', msg.hum); + } + if (msg.uv !== undefined) { + this.newValue(msg, 'uv', msg.uv); + } + if (msg.lux !== undefined) { + this.newValue(msg, 'light-intensity', msg.lux); + } + if (msg.bat !== undefined) { + this.newValue(msg, 'battery', msg.bat); + } + if (msg.rain !== undefined) { + this.newValue(msg, 'rain', msg.rain); + } + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.winsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.winsp); + } + if (msg.awinsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.awinsp); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.windir !== undefined) { + this.newValue(msg, 'wind-dir', msg.windir); + } + if (msg.co2 !== undefined) { + this.newValue(msg, 'co2', msg.co2); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + this.newValue(msg, 'switch', msg.cmd); + } + if (msg.rgbw !== undefined) { + this.newValue(msg, 'color', msg.rgbw); + this.newValue(msg, 'brightness', msg.rgbw); + } + if (msg.cmd.includes('MODE') === true ) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + if (msg.cmd.includes('DISCO') === true) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + + + + + + + } + // const features = + + }else { + logger.log(`${msg.id} n'est pas une id valide`); + } + + } + + + +} + +module.exports = { + message, +}; + diff --git a/server/services/rflink/lib/events/rflink.newValue.js b/server/services/rflink/lib/events/rflink.newValue.js new file mode 100644 index 0000000000..cff2f488c0 --- /dev/null +++ b/server/services/rflink/lib/events/rflink.newValue.js @@ -0,0 +1,24 @@ +const logger = require('../../../../utils/logger'); +const { EVENTS } = require('../../../../utils/constants'); +// eslint-disable-next-line jsdoc/require-param +/** + * @description When a new value is received. + * @param {Object} device - Device to update. + * @param {string} deviceFeature - Feature to update. + * @example + * newValue(Object, 'temperature', 30) + */ +function newValue(device, deviceFeature, state) { + + logger.debug(`RFlink : value ${deviceFeature} of device ${device.id} changed to ${state}`); + + this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, { + device_feature_external_id: `rflink:${device.id}:${deviceFeature}`, + state, + }); + +} + +module.exports = { + newValue, +}; \ No newline at end of file diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js new file mode 100644 index 0000000000..b22177ca45 --- /dev/null +++ b/server/services/rflink/lib/index.js @@ -0,0 +1,62 @@ +const logger = require('../../../utils/logger'); + +// Events + +const { newValue } = require('./events/rflink.newValue'); +const { addDevice } = require('./events/rflink.addDevice'); +const { message } = require('./events/rflink.message.js'); +const { error } = require('./events/rflink.error'); + + +// Commands + +const { setValue } = require('./commands/rflink.setValue'); +const { connect } = require('./commands/rflink.connect'); +const { disconnect } = require('./commands/rflink.disconnect'); +const { listen } = require('./commands/rflink.listen'); +const { getDevices } = require('./commands/rflink.getDevices'); + + + + + +const RFlinkManager = function RFlinkManager(usb, gladys, serviceId) { + this.usb = usb; + this.gladys = gladys; + this.scanInProgress = false; + this.serviceId = serviceId; + this.connected = false; + this.device = {}; + + +}; + +// Events + +RFlinkManager.prototype.message = message; +RFlinkManager.prototype.newValue = newValue; +RFlinkManager.prototype.addDevice = addDevice; +RFlinkManager.prototype.error = error; + + +// Commands + +RFlinkManager.prototype.setValue = setValue; +RFlinkManager.prototype.connect = connect; +RFlinkManager.prototype.disconnect = disconnect; +RFlinkManager.prototype.listen = listen; +RFlinkManager.prototype.getDevices = getDevices; + + + + + + + + + + + + + +module.exports = RFlinkManager; \ No newline at end of file diff --git a/server/services/rflink/package.json b/server/services/rflink/package.json new file mode 100644 index 0000000000..eaadda0035 --- /dev/null +++ b/server/services/rflink/package.json @@ -0,0 +1,21 @@ +{ + "author": { + "name": "mathis tondenier" + }, + "name": "gladys-rflink", + "main": "index.js", + "os": [ + "darwin", + "linux", + "win32" + ], + "cpu": [ + "x64", + "arm", + "arm64" + ], + "dependencies": { + "serialport": "8.0.6", + "@serialport/parser-readline": "8.0.6" + } +} From f69959e586d5ad5eff6a125229f5595e7d99b887 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Fri, 7 Feb 2020 19:45:03 +0100 Subject: [PATCH 072/236] fix controllers object is undefined --- server/services/rflink/index.js | 12 ++++++++---- server/services/rflink/lib/events/rflink.message.js | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 237a965fc4..524231d719 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -26,11 +26,18 @@ module.exports = function RfLink(gladys, serviceId) { const readline = new Readline(); port.pipe(readline); rfLinkManager = new RfLinkManager(readline, gladys, serviceId); + if (rfLinkManager === undefined) { throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); } else { rfLinkManager.connect(RflinkPath); + return Object.freeze({ + device : rfLinkManager, + controllers : RflinkController(gladys, rfLinkManager, serviceId), + }); } + + } @@ -48,10 +55,7 @@ module.exports = function RfLink(gladys, serviceId) { return Object.freeze({ start, - stop, - device : rfLinkManager, - controllers : RflinkController(gladys, rfLinkManager, serviceId), - + stop, }) ; }; diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 5fe1934c68..2c0d977de0 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -232,8 +232,8 @@ function message(msgRF) { read_only : false, keep_history: true, has_feedback: false, - min: 'OFF', - max: 'ON', + min: 0, + max: 1, }); From 1e0c023e34064be05dd000e42bc29fcd3cefd77d Mon Sep 17 00:00:00 2001 From: Mathis Tondenier Date: Sun, 22 Mar 2020 20:14:34 +0100 Subject: [PATCH 073/236] rebase --- server/services/rflink/index.js | 124 +-- .../rflink/lib/events/rflink.message.js | 744 +++++++++--------- 2 files changed, 434 insertions(+), 434 deletions(-) diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 524231d719..38bb560b84 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -1,62 +1,62 @@ -const logger = require('../../utils/logger'); -const RfLinkManager = require('./lib'); -const RflinkController = require('./api/rflink.controller'); -const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); - - -let rfLinkManager; - -module.exports = function RfLink(gladys, serviceId) { - - const Serialport = require('serialport'); - const Readline = require('@serialport/parser-readline'); -/** - * @description start rflink module - * @example - * gladys.services.rflink.start(); - */ - async function start() { - logger.log('Starting Rflink service'); - const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); - - if (!RflinkPath) { - throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); - } - const port = new Serialport(RflinkPath, {baudRate : 57600}); - const readline = new Readline(); - port.pipe(readline); - rfLinkManager = new RfLinkManager(readline, gladys, serviceId); - - if (rfLinkManager === undefined) { - throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); - } else { - rfLinkManager.connect(RflinkPath); - return Object.freeze({ - device : rfLinkManager, - controllers : RflinkController(gladys, rfLinkManager, serviceId), - }); - } - - - - } - - - -/** - * @description stop rfllink module - * @example - * gladys.services.rflink.stop(); - */ - async function stop() { - logger.log('Stopping Rflink service'); - rfLinkManager.disconnect(); - } - - return Object.freeze({ - start, - stop, - }) - ; -}; - +const logger = require('../../utils/logger'); +const RfLinkManager = require('./lib'); +const RflinkController = require('./api/rflink.controller'); +const { ServiceNotConfiguredError } = require('../../utils/coreErrors'); + + +let rfLinkManager; + +module.exports = function RfLink(gladys, serviceId) { + + const Serialport = require('serialport'); + const Readline = require('@serialport/parser-readline'); +/** + * @description start rflink module + * @example + * gladys.services.rflink.start(); + */ + async function start() { + logger.log('Starting Rflink service'); + const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); + + if (!RflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } + const port = new Serialport(RflinkPath, {baudRate : 57600}); + const readline = new Readline(); + port.pipe(readline); + rfLinkManager = new RfLinkManager(readline, gladys, serviceId); + + if (rfLinkManager === undefined) { + throw new ServiceNotConfiguredError('RFLINK_GATEWAY_ERROR'); + } else { + rfLinkManager.connect(RflinkPath); + return Object.freeze({ + device : rfLinkManager, + controllers : RflinkController(gladys, rfLinkManager, serviceId), + }); + } + + + + } + + + +/** + * @description stop rfllink module + * @example + * gladys.services.rflink.stop(); + */ + async function stop() { + logger.log('Stopping Rflink service'); + rfLinkManager.disconnect(); + } + + return Object.freeze({ + start, + stop, + }) + ; +}; + diff --git a/server/services/rflink/lib/events/rflink.message.js b/server/services/rflink/lib/events/rflink.message.js index 2c0d977de0..1a18702bd4 100644 --- a/server/services/rflink/lib/events/rflink.message.js +++ b/server/services/rflink/lib/events/rflink.message.js @@ -1,372 +1,372 @@ -const logger = require('../../../../utils/logger'); -const RFtoObj = require('../../api/rflink.parse.RFtoObject'); -const { - DEVICE_FEATURE_CATEGORIES, - DEVICE_FEATURE_TYPES, - DEVICE_FEATURE_UNITS, - } = require('../../../../utils/constants'); - - - -/** - * @description when a message is received by the rflink gateway - * @param {string} msgRF - The message. - * @example - * rflink.message(msg); - */ -function message(msgRF) { - - const msg = RFtoObj(msgRF); - let newDevice; - - if (typeof msg.id === 'string'){ - if (msg.id.includes('=') === false) { - - const doesntExistYet = this.device[msg.id] === undefined; - - if (doesntExistYet === true) { - - - newDevice = { - service_id : this.serviceId, - name : `${msg.protocol} `, - selector : `rflink:${msg.id}`, - external_id: `rflink:${msg.id}`, - model : `${msg.protocol}`, - features : [] - }; - - - if (msg.temp !== undefined) { - newDevice.name += 'temperature sensor'; - newDevice.features.push({ - name : 'temperature', - selector : `rflink:${msg.id}:temperature`, - external_id : `rflink:${msg.id}:temperature`, - rfcode : 'TEMP', - category : DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.CELSIUS, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.hum !== undefined) { - newDevice.features.push({ - name : 'humidity', - selector : `rflink:${msg.id}:humidity`, - external_id : `rflink:${msg.id}:humidity`, - rfcode : 'HUM', - category : DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PERCENT, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.baro !== undefined) { - newDevice.name += 'pressure sensor'; - newDevice.features.push({ - name : 'pressure', - selector : `rflink:${msg.id}:pressure`, - external_id : `rflink:${msg.id}:pressure`, - rfcode : 'BARO', - category : DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PASCAL, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100000000, - - - }); - } - if (msg.uv !== undefined) { - newDevice.name += 'uv sensor'; - newDevice.features.push({ - name : 'uv intensity', - selector : `rflink:${msg.id}:uv`, - external_id : `rflink:${msg.id}:uv`, - rfcode : 'UV', - category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.lux !== undefined) { - newDevice.name += 'light sensor'; - newDevice.features.push({ - name : 'light intensity', - selector : `rflink:${msg.id}:light-intensity`, - external_id : `rflink:${msg.id}:light-intensity`, - rfcode : 'LUX', - category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.LUX, - read_only : true, - keep_history: true, - has_feedback: false, - min: -50, - max: 100, - - - }); - } - if (msg.bat !== undefined) { - newDevice.features.push({ - name : 'battery', - selector : `rflink:${msg.id}:battery`, - external_id : `rflink:${msg.id}:battery`, - rfcode : 'BAT', - category : DEVICE_FEATURE_CATEGORIES.BATTERY, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - unit : DEVICE_FEATURE_UNITS.PERCENT, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.rain !== undefined || msg.rainrate !== undefined) { - newDevice.name += 'rain sensor'; - newDevice.features.push({ - name : 'rain', - selector : `rflink:${msg.id}:rain`, - external_id : `rflink:${msg.id}:rain`, - rfcode : 'RAIN', - category : DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 1000, - - - }); - } - if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { - newDevice.name += 'wind speed sensor'; - newDevice.features.push({ - name : 'wind speed', - selector : `rflink:${msg.id}:wind-speed`, - external_id : `rflink:${msg.id}:wind-speed`, - rfcode : 'WINSP', - category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 500, - - - }); - } - if (msg.windir !== undefined) { - newDevice.name += 'wind direction sensor'; - newDevice.features.push({ - name : 'wind direction', - selector : `rflink:${msg.id}:wind-dir`, - external_id : `rflink:${msg.id}:wind-dir`, - rfcode : 'WINDIR', - category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - } - if (msg.co2 !== undefined) { - newDevice.name += 'co2 sensor'; - newDevice.features.push({ - name : 'co2', - selector : `rflink:${msg.id}:co2`, - external_id : `rflink:${msg.id}:co2`, - rfcode : 'CO2', - category : DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, - type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, - read_only : true, - keep_history: true, - has_feedback: false, - min: 0, - max: 1000, - - - }); - } - if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { - newDevice.name += 'switch'; - newDevice.features.push({ - name : 'switch', - selector : `rflink:${msg.id}:switch`, - external_id : `rflink:${msg.id}:switch`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.SWITCH, - type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 1, - - - }); - } - - - if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { - newDevice.features.push({ - name : 'color', - selector : `rflink:${msg.id}:color`, - external_id : `rflink:${msg.id}:color`, - rfcode : 'RGBW', - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 255, - - - }); - newDevice.features.push({ - name : 'brightness', - selector : `rflink:${msg.id}:brightness`, - external_id : `rflink:${msg.id}:brightness`, - rfcode : 'RGBW', - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, - read_only : false, - keep_history: true, - has_feedback: false, - min: 0, - max: 100, - - - }); - newDevice.features.push({ - name : 'milight-mode', - selector : `rflink:${msg.id}:milight-mode`, - external_id : `rflink:${msg.id}:milight-mode`, - rfcode : 'CMD', - category : DEVICE_FEATURE_CATEGORIES.LIGHT, - type : DEVICE_FEATURE_TYPES.LIGHT.MODE, - read_only : false, - keep_history: true, - has_feedback: false, - min: 1, - max: 8, - - - }); - - - } - - - this.addDevice(newDevice); - - } else if (doesntExistYet === false) { - - if (msg.temp !== undefined) { - this.newValue(msg, 'temperature', msg.temp); - } - if (msg.hum !== undefined) { - this.newValue(msg, 'humidity', msg.hum); - } - if (msg.uv !== undefined) { - this.newValue(msg, 'uv', msg.uv); - } - if (msg.lux !== undefined) { - this.newValue(msg, 'light-intensity', msg.lux); - } - if (msg.bat !== undefined) { - this.newValue(msg, 'battery', msg.bat); - } - if (msg.rain !== undefined) { - this.newValue(msg, 'rain', msg.rain); - } - if (msg.temp !== undefined) { - this.newValue(msg, 'temperature', msg.temp); - } - if (msg.winsp !== undefined) { - this.newValue(msg, 'wind-speed', msg.winsp); - } - if (msg.awinsp !== undefined) { - this.newValue(msg, 'wind-speed', msg.awinsp); - } - if (msg.wings !== undefined) { - this.newValue(msg, 'wind-speed', msg.wings); - } - if (msg.windir !== undefined) { - this.newValue(msg, 'wind-dir', msg.windir); - } - if (msg.co2 !== undefined) { - this.newValue(msg, 'co2', msg.co2); - } - if (msg.wings !== undefined) { - this.newValue(msg, 'wind-speed', msg.wings); - } - if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { - this.newValue(msg, 'switch', msg.cmd); - } - if (msg.rgbw !== undefined) { - this.newValue(msg, 'color', msg.rgbw); - this.newValue(msg, 'brightness', msg.rgbw); - } - if (msg.cmd.includes('MODE') === true ) { - this.newValue(msg, 'milight-mode', msg.cmd); - } - if (msg.cmd.includes('DISCO') === true) { - this.newValue(msg, 'milight-mode', msg.cmd); - } - - - - - - - } - // const features = - - }else { - logger.log(`${msg.id} n'est pas une id valide`); - } - - } - - - -} - -module.exports = { - message, -}; - +const logger = require('../../../../utils/logger'); +const RFtoObj = require('../../api/rflink.parse.RFtoObject'); +const { + DEVICE_FEATURE_CATEGORIES, + DEVICE_FEATURE_TYPES, + DEVICE_FEATURE_UNITS, + } = require('../../../../utils/constants'); + + + +/** + * @description when a message is received by the rflink gateway + * @param {string} msgRF - The message. + * @example + * rflink.message(msg); + */ +function message(msgRF) { + + const msg = RFtoObj(msgRF); + let newDevice; + + if (typeof msg.id === 'string'){ + if (msg.id.includes('=') === false) { + + const doesntExistYet = this.device[msg.id] === undefined; + + if (doesntExistYet === true) { + + + newDevice = { + service_id : this.serviceId, + name : `${msg.protocol} `, + selector : `rflink:${msg.id}`, + external_id: `rflink:${msg.id}`, + model : `${msg.protocol}`, + features : [] + }; + + + if (msg.temp !== undefined) { + newDevice.name += 'temperature sensor'; + newDevice.features.push({ + name : 'temperature', + selector : `rflink:${msg.id}:temperature`, + external_id : `rflink:${msg.id}:temperature`, + rfcode : 'TEMP', + category : DEVICE_FEATURE_CATEGORIES.TEMPERATURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.CELSIUS, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.hum !== undefined) { + newDevice.features.push({ + name : 'humidity', + selector : `rflink:${msg.id}:humidity`, + external_id : `rflink:${msg.id}:humidity`, + rfcode : 'HUM', + category : DEVICE_FEATURE_CATEGORIES.HUMIDITY_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.baro !== undefined) { + newDevice.name += 'pressure sensor'; + newDevice.features.push({ + name : 'pressure', + selector : `rflink:${msg.id}:pressure`, + external_id : `rflink:${msg.id}:pressure`, + rfcode : 'BARO', + category : DEVICE_FEATURE_CATEGORIES.PRESSURE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PASCAL, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100000000, + + + }); + } + if (msg.uv !== undefined) { + newDevice.name += 'uv sensor'; + newDevice.features.push({ + name : 'uv intensity', + selector : `rflink:${msg.id}:uv`, + external_id : `rflink:${msg.id}:uv`, + rfcode : 'UV', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.lux !== undefined) { + newDevice.name += 'light sensor'; + newDevice.features.push({ + name : 'light intensity', + selector : `rflink:${msg.id}:light-intensity`, + external_id : `rflink:${msg.id}:light-intensity`, + rfcode : 'LUX', + category : DEVICE_FEATURE_CATEGORIES.LIGHT_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.LUX, + read_only : true, + keep_history: true, + has_feedback: false, + min: -50, + max: 100, + + + }); + } + if (msg.bat !== undefined) { + newDevice.features.push({ + name : 'battery', + selector : `rflink:${msg.id}:battery`, + external_id : `rflink:${msg.id}:battery`, + rfcode : 'BAT', + category : DEVICE_FEATURE_CATEGORIES.BATTERY, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + unit : DEVICE_FEATURE_UNITS.PERCENT, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.rain !== undefined || msg.rainrate !== undefined) { + newDevice.name += 'rain sensor'; + newDevice.features.push({ + name : 'rain', + selector : `rflink:${msg.id}:rain`, + external_id : `rflink:${msg.id}:rain`, + rfcode : 'RAIN', + category : DEVICE_FEATURE_CATEGORIES.RAIN_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.winsp !== undefined || msg.awinsp !== undefined || msg.wings !== undefined) { + newDevice.name += 'wind speed sensor'; + newDevice.features.push({ + name : 'wind speed', + selector : `rflink:${msg.id}:wind-speed`, + external_id : `rflink:${msg.id}:wind-speed`, + rfcode : 'WINSP', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 500, + + + }); + } + if (msg.windir !== undefined) { + newDevice.name += 'wind direction sensor'; + newDevice.features.push({ + name : 'wind direction', + selector : `rflink:${msg.id}:wind-dir`, + external_id : `rflink:${msg.id}:wind-dir`, + rfcode : 'WINDIR', + category : DEVICE_FEATURE_CATEGORIES.WIND_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + } + if (msg.co2 !== undefined) { + newDevice.name += 'co2 sensor'; + newDevice.features.push({ + name : 'co2', + selector : `rflink:${msg.id}:co2`, + external_id : `rflink:${msg.id}:co2`, + rfcode : 'CO2', + category : DEVICE_FEATURE_CATEGORIES.SMOKE_SENSOR, + type : DEVICE_FEATURE_TYPES.SENSOR.INTEGER, + read_only : true, + keep_history: true, + has_feedback: false, + min: 0, + max: 1000, + + + }); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + newDevice.name += 'switch'; + newDevice.features.push({ + name : 'switch', + selector : `rflink:${msg.id}:switch`, + external_id : `rflink:${msg.id}:switch`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.SWITCH, + type : DEVICE_FEATURE_TYPES.SENSOR.BINARY, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 1, + + + }); + } + + + if (msg.rgbw !== undefined || msg.cmd.includes('MODE') === true || msg.cmd.includes('DISCO') === true) { + newDevice.features.push({ + name : 'color', + selector : `rflink:${msg.id}:color`, + external_id : `rflink:${msg.id}:color`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.COLOR, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 255, + + + }); + newDevice.features.push({ + name : 'brightness', + selector : `rflink:${msg.id}:brightness`, + external_id : `rflink:${msg.id}:brightness`, + rfcode : 'RGBW', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.BRIGHTNESS, + read_only : false, + keep_history: true, + has_feedback: false, + min: 0, + max: 100, + + + }); + newDevice.features.push({ + name : 'milight-mode', + selector : `rflink:${msg.id}:milight-mode`, + external_id : `rflink:${msg.id}:milight-mode`, + rfcode : 'CMD', + category : DEVICE_FEATURE_CATEGORIES.LIGHT, + type : DEVICE_FEATURE_TYPES.LIGHT.MODE, + read_only : false, + keep_history: true, + has_feedback: false, + min: 1, + max: 8, + + + }); + + + } + + + this.addDevice(newDevice); + + } else if (doesntExistYet === false) { + + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.hum !== undefined) { + this.newValue(msg, 'humidity', msg.hum); + } + if (msg.uv !== undefined) { + this.newValue(msg, 'uv', msg.uv); + } + if (msg.lux !== undefined) { + this.newValue(msg, 'light-intensity', msg.lux); + } + if (msg.bat !== undefined) { + this.newValue(msg, 'battery', msg.bat); + } + if (msg.rain !== undefined) { + this.newValue(msg, 'rain', msg.rain); + } + if (msg.temp !== undefined) { + this.newValue(msg, 'temperature', msg.temp); + } + if (msg.winsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.winsp); + } + if (msg.awinsp !== undefined) { + this.newValue(msg, 'wind-speed', msg.awinsp); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.windir !== undefined) { + this.newValue(msg, 'wind-dir', msg.windir); + } + if (msg.co2 !== undefined) { + this.newValue(msg, 'co2', msg.co2); + } + if (msg.wings !== undefined) { + this.newValue(msg, 'wind-speed', msg.wings); + } + if (msg.switch !== undefined && msg.cmd === 'ON' || msg.cmd === 'OFF' || msg.cmd === 'ALLON' || msg.cmd === 'ALLOFF') { + this.newValue(msg, 'switch', msg.cmd); + } + if (msg.rgbw !== undefined) { + this.newValue(msg, 'color', msg.rgbw); + this.newValue(msg, 'brightness', msg.rgbw); + } + if (msg.cmd.includes('MODE') === true ) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + if (msg.cmd.includes('DISCO') === true) { + this.newValue(msg, 'milight-mode', msg.cmd); + } + + + + + + + } + // const features = + + }else { + logger.log(`${msg.id} n'est pas une id valide`); + } + + } + + + +} + +module.exports = { + message, +}; + From b8947f7a7334087f8fb379a50df06cf7cfddf48a Mon Sep 17 00:00:00 2001 From: Mathis Tondenier Date: Sun, 22 Mar 2020 20:12:51 +0100 Subject: [PATCH 074/236] new commit for rebase --- server/package-lock.json | 45 +- .../services/rflink/api/rflink.controller.js | 142 ++--- .../rflink/api/rflink.parse.ObjToRF.js | 48 +- .../rflink/api/rflink.parse.RFtoObject.js | 88 +-- server/services/rflink/index.js | 44 +- .../rflink/lib/commands/rflink.connect.js | 61 +- .../rflink/lib/commands/rflink.disconnect.js | 38 +- .../rflink/lib/commands/rflink.getDevices.js | 30 +- .../rflink/lib/commands/rflink.listen.js | 34 +- .../rflink/lib/events/rflink.addDevice.js | 74 +-- .../rflink/lib/events/rflink.connected.js | 40 +- .../rflink/lib/events/rflink.error.js | 40 +- server/services/rflink/lib/index.js | 121 ++-- server/services/rflink/package-lock.json | 553 ++++++++++++++++++ server/services/rflink/package.json | 42 +- 15 files changed, 978 insertions(+), 422 deletions(-) create mode 100644 server/services/rflink/package-lock.json diff --git a/server/package-lock.json b/server/package-lock.json index b655ec85f3..3cb04ff289 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -874,9 +874,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" }, "axios": { "version": "0.18.1", @@ -7756,9 +7756,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" }, "pstree.remy": { "version": "1.1.7", @@ -8191,9 +8191,9 @@ } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -8202,7 +8202,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -8212,7 +8212,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, @@ -8975,9 +8975,9 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sqlite3": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.0.tgz", - "integrity": "sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz", + "integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==", "requires": { "nan": "^2.12.1", "node-pre-gyp": "^0.11.0", @@ -9601,19 +9601,12 @@ } }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tracer": { diff --git a/server/services/rflink/api/rflink.controller.js b/server/services/rflink/api/rflink.controller.js index e59aee8b58..2b4a25f16d 100644 --- a/server/services/rflink/api/rflink.controller.js +++ b/server/services/rflink/api/rflink.controller.js @@ -1,72 +1,72 @@ -const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); -const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); -const logger = require('../../../utils/logger'); - -module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { - /** - * @api {get} /api/v1/service/rflink/devices get rflink devices - * @apiName getDevices - * @apiGroup RFlink - */ - async function getDevices(req, res) { - logger.log('getting devices ...'); - res.json(RFlinkManager.getDevices()); - } - - /** - * @api {get} /api/v1/service/rflink/connect connect to the gateway - * @apiName connect - * @apiGroup RFlink - */ - async function connect(req, res) { - const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); - if (!rflinkPath) { - throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); - } - RFlinkManager.connect(rflinkPath); - res.json({succes: true, }); - } - /** - * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway - * @apiName disconnect - * @apiGroup RFlink - */ - async function disconnect(req, res) { - RFlinkManager.disconnect(); - res.json({ - success: true, - }); - } - /** - * @api {get} /api/v1/service/rflink/status get the gateway's status - * @apiName getStatus - * @apiGroup RFlink - */ - async function getStatus(req, res) { - logger.log('getting status'); - res.json({ - connected: RFlinkManager.connected, - scanInProgress: RFlinkManager.scanInProgress, - }); - } - - return { - 'get /api/v1/service/rflink/devices' : { - authenticated: true, - controller: asyncMiddleware(getDevices) - }, - 'post /api/v1/service/rflink/connect' : { - authenticated: true, - controller: asyncMiddleware(connect) - }, - 'post /api/v1/service/rflink/disconnect' : { - authenticated: true, - controller: asyncMiddleware(disconnect) - }, - 'get /api/v1/sevice/rflink/status' : { - authenticated: true, - controller: asyncMiddleware(getStatus) - } - - }; +const asyncMiddleware = require('../../../api/middlewares/asyncMiddleware'); +const { ServiceNotConfiguredError } = require('../../../utils/coreErrors'); +const logger = require('../../../utils/logger'); + +module.exports = function RFlinkController(gladys, RFlinkManager, serviceID) { + /** + * @api {get} /api/v1/service/rflink/devices get rflink devices + * @apiName getDevices + * @apiGroup RFlink + */ + async function getDevices(req, res) { + logger.log('getting devices ...'); + res.json(RFlinkManager.getDevices()); + } + + /** + * @api {get} /api/v1/service/rflink/connect connect to the gateway + * @apiName connect + * @apiGroup RFlink + */ + async function connect(req, res) { + const rflinkPath = await gladys.variable.getValue('RFLINK_PATH'); + if (!rflinkPath) { + throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); + } + RFlinkManager.connect(rflinkPath); + res.json({succes: true, }); + } + /** + * @api {get} /api/v1/service/rflink/disconnect discconnect the gateway + * @apiName disconnect + * @apiGroup RFlink + */ + async function disconnect(req, res) { + RFlinkManager.disconnect(); + res.json({ + success: true, + }); + } + /** + * @api {get} /api/v1/service/rflink/status get the gateway's status + * @apiName getStatus + * @apiGroup RFlink + */ + async function getStatus(req, res) { + logger.log('getting status'); + res.json({ + connected: RFlinkManager.connected, + scanInProgress: RFlinkManager.scanInProgress, + }); + } + + return { + 'get /api/v1/service/rflink/devices' : { + authenticated: true, + controller: asyncMiddleware(getDevices) + }, + 'post /api/v1/service/rflink/connect' : { + authenticated: true, + controller: asyncMiddleware(connect) + }, + 'post /api/v1/service/rflink/disconnect' : { + authenticated: true, + controller: asyncMiddleware(disconnect) + }, + 'get /api/v1/sevice/rflink/status' : { + authenticated: true, + controller: asyncMiddleware(getStatus) + } + + }; }; \ No newline at end of file diff --git a/server/services/rflink/api/rflink.parse.ObjToRF.js b/server/services/rflink/api/rflink.parse.ObjToRF.js index f01c8f32b2..5c3bef15b3 100644 --- a/server/services/rflink/api/rflink.parse.ObjToRF.js +++ b/server/services/rflink/api/rflink.parse.ObjToRF.js @@ -1,25 +1,25 @@ - -// eslint-disable-next-line jsdoc/check-alignment -/** -* @description convert a rflink device object to a string that can be sent to rflink -* @param {Object} device - Secure node. -* @example -* rflink.ObjToRF(device); -*/ -function ObjToRF(device) { - const id = device.external_id.slit(':')[1]; - - let Rfcode = `10;${device.model};${id};`; - - for (let i = 0;i { - this.message(data); - - - - }); -} - -module.exports = { - listen, +const logger = require('../../../../utils/logger'); +/** + * @description listen + * @example + * rflink.listen(); + */ +function listen () { + this.usb.on('data', (data) => { + this.message(data); + + + + }); +} + +module.exports = { + listen, }; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.addDevice.js b/server/services/rflink/lib/events/rflink.addDevice.js index e8b29e75f6..085fd75bb1 100644 --- a/server/services/rflink/lib/events/rflink.addDevice.js +++ b/server/services/rflink/lib/events/rflink.addDevice.js @@ -1,38 +1,38 @@ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable no-restricted-syntax */ -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - - -/** - * @description Add device. - * @param {Object} device - Device to add. - * @example - * Rflink.addDevice(device); - */ -function addDevice(device) { - const id = device.external_id.split(':')[1]; - - - this.gladys.event.emit(EVENTS.DEVICE.NEW, - device, - ); - - this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, - payload : device, - }); - - this.device[id] = device; - - - - - - - - - - -} - +/* eslint-disable no-prototype-builtins */ +/* eslint-disable no-restricted-syntax */ +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + + +/** + * @description Add device. + * @param {Object} device - Device to add. + * @example + * Rflink.addDevice(device); + */ +function addDevice(device) { + const id = device.external_id.split(':')[1]; + + + this.gladys.event.emit(EVENTS.DEVICE.NEW, + device, + ); + + this.gladys.event.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type : WEBSOCKET_MESSAGE_TYPES.RFLINK.NEW_DEVICE, + payload : device, + }); + + this.device[id] = device; + + + + + + + + + + +} + module.exports = {addDevice}; \ No newline at end of file diff --git a/server/services/rflink/lib/events/rflink.connected.js b/server/services/rflink/lib/events/rflink.connected.js index 9770f9359f..4e98ad602a 100644 --- a/server/services/rflink/lib/events/rflink.connected.js +++ b/server/services/rflink/lib/events/rflink.connected.js @@ -1,20 +1,20 @@ -const logger = require('../../../../utils/logger'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - -/** - * @description When the gateway is connected - * @example - * rflink.on('connected', this.connected); - */ -function connected() { - logger.debug(`Rflink : Gateway is connected`); - this.connected = true; - this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, - payload: {}, - }); -} - -module.exports = { - connected, -}; +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When the gateway is connected + * @example + * rflink.on('connected', this.connected); + */ +function connected() { + logger.debug(`Rflink : Gateway is connected`); + this.connected = true; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.CONNECTED, + payload: {}, + }); +} + +module.exports = { + connected, +}; diff --git a/server/services/rflink/lib/events/rflink.error.js b/server/services/rflink/lib/events/rflink.error.js index 52f363e50f..74f290bf9d 100644 --- a/server/services/rflink/lib/events/rflink.error.js +++ b/server/services/rflink/lib/events/rflink.error.js @@ -1,20 +1,20 @@ -const logger = require('../../../../utils/logger'); -const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); - -/** - * @description When an error occur - * @example - * rflink.on('failed', this.driverFailed); - */ -function failed() { - logger.debug(`RFlink: Failed to start`); - this.connected = false; - this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { - type: WEBSOCKET_MESSAGE_TYPES.RFLINK.ERROR, - payload: {}, - }); -} - -module.exports = { - failed, -}; +const logger = require('../../../../utils/logger'); +const { EVENTS, WEBSOCKET_MESSAGE_TYPES } = require('../../../../utils/constants'); + +/** + * @description When an error occur + * @example + * rflink.on('failed', this.driverFailed); + */ +function failed() { + logger.debug(`RFlink: Failed to start`); + this.connected = false; + this.eventManager.emit(EVENTS.WEBSOCKET.SEND_ALL, { + type: WEBSOCKET_MESSAGE_TYPES.RFLINK.ERROR, + payload: {}, + }); +} + +module.exports = { + failed, +}; diff --git a/server/services/rflink/lib/index.js b/server/services/rflink/lib/index.js index b22177ca45..71227e3bb6 100644 --- a/server/services/rflink/lib/index.js +++ b/server/services/rflink/lib/index.js @@ -1,62 +1,61 @@ -const logger = require('../../../utils/logger'); - -// Events - -const { newValue } = require('./events/rflink.newValue'); -const { addDevice } = require('./events/rflink.addDevice'); -const { message } = require('./events/rflink.message.js'); -const { error } = require('./events/rflink.error'); - - -// Commands - -const { setValue } = require('./commands/rflink.setValue'); -const { connect } = require('./commands/rflink.connect'); -const { disconnect } = require('./commands/rflink.disconnect'); -const { listen } = require('./commands/rflink.listen'); -const { getDevices } = require('./commands/rflink.getDevices'); - - - - - -const RFlinkManager = function RFlinkManager(usb, gladys, serviceId) { - this.usb = usb; - this.gladys = gladys; - this.scanInProgress = false; - this.serviceId = serviceId; - this.connected = false; - this.device = {}; - - -}; - -// Events - -RFlinkManager.prototype.message = message; -RFlinkManager.prototype.newValue = newValue; -RFlinkManager.prototype.addDevice = addDevice; -RFlinkManager.prototype.error = error; - - -// Commands - -RFlinkManager.prototype.setValue = setValue; -RFlinkManager.prototype.connect = connect; -RFlinkManager.prototype.disconnect = disconnect; -RFlinkManager.prototype.listen = listen; -RFlinkManager.prototype.getDevices = getDevices; - - - - - - - - - - - - - + +// Events + +const { newValue } = require('./events/rflink.newValue'); +const { addDevice } = require('./events/rflink.addDevice'); +const { message } = require('./events/rflink.message.js'); +const { error } = require('./events/rflink.error'); + + +// Commands + +const { setValue } = require('./commands/rflink.setValue'); +const { connect } = require('./commands/rflink.connect'); +const { disconnect } = require('./commands/rflink.disconnect'); +const { listen } = require('./commands/rflink.listen'); +const { getDevices } = require('./commands/rflink.getDevices'); + + + + + +const RFlinkManager = function RFlinkManager(gladys, serviceId) { + this.gladys = gladys; + this.scanInProgress = false; + this.serviceId = serviceId; + this.connected = false; + this.scanInProgress = false; + this.device = {}; + + +}; + +// Events + +RFlinkManager.prototype.message = message; +RFlinkManager.prototype.newValue = newValue; +RFlinkManager.prototype.addDevice = addDevice; +RFlinkManager.prototype.error = error; + + +// Commands + +RFlinkManager.prototype.setValue = setValue; +RFlinkManager.prototype.connect = connect; +RFlinkManager.prototype.disconnect = disconnect; +RFlinkManager.prototype.listen = listen; +RFlinkManager.prototype.getDevices = getDevices; + + + + + + + + + + + + + module.exports = RFlinkManager; \ No newline at end of file diff --git a/server/services/rflink/package-lock.json b/server/services/rflink/package-lock.json new file mode 100644 index 0000000000..fab23c89c5 --- /dev/null +++ b/server/services/rflink/package-lock.json @@ -0,0 +1,553 @@ +{ + "name": "gladys-rflink", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@serialport/binding-abstract": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-8.0.6.tgz", + "integrity": "sha512-1swwUVoRyQ9ubxrkJ8JPppykohUpTAP4jkGr36e9NjbVocSPfqeX6tFZFwl/IdUlwJwxGdbKDqq7FvXniCQUMw==", + "requires": { + "debug": "^4.1.1" + } + }, + "@serialport/binding-mock": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-8.0.6.tgz", + "integrity": "sha512-BIbY5/PsDDo0QWDNCCxDgpowAdks+aZR8BOsEtK2GoASTTcJCy1fBwPIfH870o7rnbH901wY3C+yuTfdOvSO9A==", + "requires": { + "@serialport/binding-abstract": "^8.0.6", + "debug": "^4.1.1" + } + }, + "@serialport/bindings": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-8.0.7.tgz", + "integrity": "sha512-IqudDL8ne2Y2S0W5fKA6wdgHCIA2e2OIaPVYhGy6duE6legNHFY+05CLicHAyAeTocXmHU7rVNxzVQrOG5tM4g==", + "requires": { + "@serialport/binding-abstract": "^8.0.6", + "@serialport/parser-readline": "^8.0.6", + "bindings": "^1.5.0", + "debug": "^4.1.1", + "nan": "^2.14.0", + "prebuild-install": "^5.3.0" + } + }, + "@serialport/parser-byte-length": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-8.0.6.tgz", + "integrity": "sha512-92mrFxFEvq3gRvSM7ANK/jfbmHslz91a5oYJy/nbSn4H/MCRXjxR2YOkQgVXuN+zLt+iyDoW3pcOP4Sc1nWdqQ==" + }, + "@serialport/parser-cctalk": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-8.0.6.tgz", + "integrity": "sha512-pqtCYQPgxnxHygiXUPCfgX7sEx+fdR/ObjpscidynEULUq2fFrC5kBkrxRbTfHRtTaU2ii9DyjFq0JVRCbhI0Q==" + }, + "@serialport/parser-delimiter": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-8.0.6.tgz", + "integrity": "sha512-ogKOcPisPMlVtirkuDu3SFTF0+xT0ijxoH7XjpZiYL41EVi367MwuCnEmXG+dEKKnF0j9EPqOyD2LGSJxaFmhQ==" + }, + "@serialport/parser-readline": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-8.0.6.tgz", + "integrity": "sha512-OYBT2mpczh9QUI3MTw8j0A0tIlPVjpVipvuVnjRkYwxrxPeq04RaLFhaDpuRzua5rTKMt89c1y3btYeoDXMjAA==", + "requires": { + "@serialport/parser-delimiter": "^8.0.6" + } + }, + "@serialport/parser-ready": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-8.0.6.tgz", + "integrity": "sha512-xcEqv4rc119WR5JzAuu8UeJOlAwET2PTdNb6aIrrLlmTxhvuBbuRFcsnF3BpH9jUL30Kh7a6QiNXIwVG+WR/1Q==" + }, + "@serialport/parser-regex": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-8.0.6.tgz", + "integrity": "sha512-J8KY75Azz5ZyExmyM5YfUxbXOWBkZCytKgCCmZ966ttwZS0bUZOuoCaZj2Zp4VILJAiLuxHoqc0foi67Fri5+g==" + }, + "@serialport/stream": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-8.0.6.tgz", + "integrity": "sha512-ym1PwM0rwLrj90vRBB66I1hwMXbuMw9wGTxqns75U3N/tuNFOH85mxXaYVF2TpI66aM849NoI1jMm50fl9equg==", + "requires": { + "debug": "^4.1.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.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" + } + } + } + }, + "buffer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", + "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "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==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node-abi": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", + "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", + "requires": { + "semver": "^5.4.1" + } + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "prebuild-install": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "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" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "serialport": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-8.0.6.tgz", + "integrity": "sha512-TDdP374kZ4+iYKqZbx47eSnJEMoQQfZwztAikWFjZORU1HokOSc3KVuyACy1yX2cVK6u+3C7PmBK3N31CLxMWg==", + "requires": { + "@serialport/binding-mock": "^8.0.6", + "@serialport/bindings": "^8.0.6", + "@serialport/parser-byte-length": "^8.0.6", + "@serialport/parser-cctalk": "^8.0.6", + "@serialport/parser-delimiter": "^8.0.6", + "@serialport/parser-readline": "^8.0.6", + "@serialport/parser-ready": "^8.0.6", + "@serialport/parser-regex": "^8.0.6", + "@serialport/stream": "^8.0.6", + "debug": "^4.1.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "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" + } + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/server/services/rflink/package.json b/server/services/rflink/package.json index eaadda0035..bd7f8f586c 100644 --- a/server/services/rflink/package.json +++ b/server/services/rflink/package.json @@ -1,21 +1,21 @@ -{ - "author": { - "name": "mathis tondenier" - }, - "name": "gladys-rflink", - "main": "index.js", - "os": [ - "darwin", - "linux", - "win32" - ], - "cpu": [ - "x64", - "arm", - "arm64" - ], - "dependencies": { - "serialport": "8.0.6", - "@serialport/parser-readline": "8.0.6" - } -} +{ + "author": { + "name": "mathis tondenier" + }, + "name": "gladys-rflink", + "main": "index.js", + "os": [ + "darwin", + "linux", + "win32" + ], + "cpu": [ + "x64", + "arm", + "arm64" + ], + "dependencies": { + "serialport": "8.0.6", + "@serialport/parser-readline": "8.0.6" + } +} From 8a44a6abce78955b5ad583c62b3687c402896533 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Thu, 19 Mar 2020 17:26:22 +0100 Subject: [PATCH 075/236] prettier --- front/src/config/demo.json | 22 +++++ front/src/config/i18n/en.json | 95 +++++++++++++++++++- front/src/config/integrations/device.en.json | 6 ++ 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/front/src/config/demo.json b/front/src/config/demo.json index 13f9bd67c5..59bf6fe1c3 100644 --- a/front/src/config/demo.json +++ b/front/src/config/demo.json @@ -626,6 +626,28 @@ ] } ], + "get /api/v1/service/rflink/device" : [ + { + "id": "fbedb47f-4d25-4381-8923-2633b23192a0", + "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", + "room_id": "cecc52c7-3e67-4b75-9b13-9a8867b0443d", + "name": "PC bureau", + "selector": "rflink:1234", + "external_id": "rflink:86aa6:switch:10", + "should_poll": false, + "poll_frequency": null, + "created_at": "2019-02-12T07:49:07.556Z", + "updated_at": "2019-02-12T07:49:07.556Z", + "features": [ + { + "name": "power", + "selector": "switch-test", + "category": "switch", + "type": "binary" + } + ] + } + ], "get /api/v1/service/zwave/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 2094cbfc2b..aa32620113 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -174,6 +174,79 @@ "navigation": "navigation" } }, + "rflink": { + "title": "RFlink", + "settingsTab": "Settings", + "deviceTab": "devices", + "settings": { + "title": "Rflink Usb Settings", + "description": "To use Rflink in Gladys, you need to have a Rflink gateway connected to your Gladys instance.", + "zwaveUsbDriverPathLabel": "Select the USB port where your Rflink is connected", + "connectButton": "Connect", + "disconnectButton": "Disconnect", + "refreshButton": "Refresh USB list", + "notConnected": "Gladys is not connected to any Rflink Gateway.", + "connectedWithSuccess": "Rflink Gateway connected with success.", + "connecting": "Trying to connect to Rflink Gateway ...", + "driverFailedError": "An error occured while trying to connect to Rflink Gateway .", + "debug": { + "title": "Rflink debug console", + "info": "Here you can see the last command that Rflink sent to Gladys it's in this form : 20;02;MODEL;ID;LABEL=data;LABEL1=data1; ...", + "placeholder": "message to send", + "sendButton": "Send" + }, + "milight": { + "title": "Rflink Milight settings", + "gatewayBarinfo": "Gateway number: ", + "zoneInfo": "Gateway zone: ", + "about": " You can use your actual milight bridge id or if you don't have a gateway , use a new one (F746 by default) the id is just a code to identify the gateway. You can use unlimited milight but each bridge has 4 zones", + "pairButton": "Pair", + "unpairButton": "Unpair" + } + }, + "feature": { + "nameLabel": "Name", + "model": "Model", + "message": "You can only create actuators , sensors are automatically detected and added in the device tab when they send messages", + "namePlaceholder": "Enter feature name", + "switchIdLabel": "ID ", + "switchIdMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", + "switchIdPlaceholder": "Rflink device ID", + "switchNumberLabel": "Channel", + "switchNumberMessage": "The feature external ID is an unique ID which is used to control the device, it is always in the form : 'rflink:id:type:channel'.Read the documentation here : not online for the moment", + "switchNumberPlaceholder": "value of SWITCH in debug console (settings tab) ", + "unitLabel": "Unit", + "minLabel": "Minimum value", + "minPlaceholder": "Enter feature minimum value", + "maxLabel": "Maximum value", + "maxPlaceholder": "Enter feature maximum value", + "addButton": "Add feature", + "deleteLabel": "Delete feature" + }, + "device": { + "title": "Devices in Gladys", + "deviceOnNetworkTitle": "Devices detected by the gateway", + "connectButton": "Connect/Reconnect", + "search": "Search devices", + "deviceNotHandled": "Device not handled yet, please contact us to help us connect it in Gladys!", + "noDevices": "No devices found. Make sure you have a connected Rflink gateway in the setup tab.", + "scanButton": "Scan", + "nameLabel": "Name", + "featuresLabel": "Features", + "noFeatures": "No features", + "newButton": "New", + "saveButton": "Save", + "deleteButton": "Delete", + "editButton": "Edit", + "noNameLabel": "No name", + "roomLabel": "Room", + "returnButton": "Return back", + "notFound": "Requested device not found.", + "backToList": "Back to device list", + "saveError": "Error saving or deleting device", + "saveConflictError": "Conflict: Are you sure all device feature external IDs are unique ?" + } + }, "telegram": { "title": "Telegram", "introduction": "To connect Gladys to Telegram, you first need to create a Telegram bot using the Botfather. Send a /newbot message to the @BotFather in Telegram. Then, enter the API key he gave you below.", @@ -436,12 +509,12 @@ "selectTriggerLabel": "Select a trigger type", "newAction": "New action", "selectActionType": "Select an action type", - "addActionButton": "Add action", + "addActionButton": "Add new action", "noTriggersYet": "No trigger added yet. It's not mandatory to have a trigger in a scene.", "noActionsYet": "No actions added yet. Click on the + button to add an action to this scene.", "triggersDescription": "Every trigger is independent. When any of those triggers is triggered, the scene will run.", "actionsDescription": "All actions in this block will run in parallel. To make a sequence of actions, add actions to the next block.", - "addNewTriggerButton": "Add trigger", + "addNewTriggerButton": "Add new trigger", "saveSceneError": "There was an error saving your scene. Please check that all actions/triggers are filled and correct.", "actionsCard": { "delay": { @@ -555,6 +628,24 @@ "sunday": "Sunday" } } + }, + "triggers": { + "device": { + "new-state": "Device state change" + } + }, + "triggersCard": { + "newState": { + "equal": "equal", + "superior": "superior", + "superiorOrEqual": "superior or equal", + "less": "less", + "lessOrEqual": "less or equal", + "different": "different", + "valuePlaceholder": "Value", + "on": "On", + "off": "Off" + } } }, "profile": { diff --git a/front/src/config/integrations/device.en.json b/front/src/config/integrations/device.en.json index c332fecb7c..af1c94abb1 100644 --- a/front/src/config/integrations/device.en.json +++ b/front/src/config/integrations/device.en.json @@ -34,5 +34,11 @@ "name": "Sonoff", "description": "Control your Sonoff devices.", "img": "/assets/integrations/cover/sonoff.jpg" + }, + { + "key": "rflink", + "name": "rflink", + "description": "Control your rflink devices.", + "img": "/assets/integrations/cover/rflink.png" } ] From 800b6b7a8f34c735a41688a318301ecdcd26dac4 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier Date: Mon, 23 Mar 2020 07:59:57 +0100 Subject: [PATCH 076/236] prettier --- front/package-lock.json | 248 +++-- front/package.json | 3 +- front/src/config/demo.json | 4 +- server/services/rflink/package-lock.json | 1096 +++++++++++----------- 4 files changed, 699 insertions(+), 652 deletions(-) diff --git a/front/package-lock.json b/front/package-lock.json index cd72df57de..6505bea43a 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -349,9 +349,10 @@ } }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true }, "acorn-dynamic-import": { "version": "2.0.2", @@ -777,9 +778,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -2377,6 +2378,16 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bl": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", @@ -4462,9 +4473,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -5772,6 +5783,13 @@ "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", "dev": true }, + "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==", + "dev": true, + "optional": true + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -6042,14 +6060,15 @@ "dev": true }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", + "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -6097,7 +6116,7 @@ } }, "chownr": { - "version": "1.1.1", + "version": "1.1.4", "bundled": true, "dev": true, "optional": true @@ -6127,7 +6146,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "dev": true, "optional": true, @@ -6154,12 +6173,12 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -6185,7 +6204,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "dev": true, "optional": true, @@ -6214,7 +6233,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true, @@ -6233,7 +6252,7 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, "dev": true, "optional": true @@ -6269,13 +6288,13 @@ } }, "minimist": { - "version": "0.0.8", + "version": "1.2.5", "bundled": true, "dev": true, "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, "optional": true, @@ -6285,42 +6304,42 @@ } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { - "version": "0.5.1", + "version": "0.5.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "optional": true, @@ -6334,11 +6353,11 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { - "version": "4.0.1", + "version": "4.0.3", "bundled": true, "dev": true, "optional": true, @@ -6348,19 +6367,29 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.8", "bundled": true, "dev": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npmlog": { @@ -6425,7 +6454,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "optional": true @@ -6440,18 +6469,10 @@ "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } } }, "readable-stream": { - "version": "2.3.6", + "version": "2.3.7", "bundled": true, "dev": true, "optional": true, @@ -6466,7 +6487,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "optional": true, @@ -6493,7 +6514,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "dev": true, "optional": true @@ -6546,18 +6567,18 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -6582,7 +6603,7 @@ "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, "dev": true, "optional": true @@ -6824,9 +6845,9 @@ "dev": true }, "handlebars": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz", - "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz", + "integrity": "sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -7566,9 +7587,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -9498,9 +9519,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } @@ -9802,9 +9823,9 @@ }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } @@ -9913,9 +9934,9 @@ } }, "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true }, "minipass": { @@ -9985,12 +10006,20 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", "dev": true, "requires": { - "minimist": "0.0.8" + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } } }, "moment": { @@ -10078,9 +10107,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -10237,6 +10266,13 @@ "acorn-es7-plugin": "^1.1.7", "nodent-transform": "^3.2.9", "source-map": "^0.5.7" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + } } }, "nodent-runtime": { @@ -12502,9 +12538,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } @@ -13281,9 +13317,9 @@ } }, "preact": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.3.2.tgz", - "integrity": "sha512-yIx4i7gp45enhzX4SLkvvR20UZ+YOUbMdj2KEscU/dC70MHv/L6dpTcsP+4sXrU9SRbA3GjJQQCPfFa5sE17dQ==" + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.3.4.tgz", + "integrity": "sha512-wMgzs/RGYf0I1PZf8ZFJdyU/3kCcwepJyVYe+N9FGajyQWarMoPrPfrQajcG0psPj6ySYv2cSuLYFCihvV/Qrw==" }, "preact-cli": { "version": "2.2.1", @@ -14042,9 +14078,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -14113,13 +14149,23 @@ }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, "react-big-calendar": { "version": "0.22.1", "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.22.1.tgz", @@ -14586,9 +14632,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -15324,9 +15370,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -15351,9 +15397,9 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } @@ -16837,9 +16883,9 @@ "dev": true }, "uglify-js": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", - "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "dev": true, "optional": true, "requires": { @@ -17389,9 +17435,9 @@ }, "dependencies": { "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true } } diff --git a/front/package.json b/front/package.json index de1e4eb6c6..ffe652dc8d 100644 --- a/front/package.json +++ b/front/package.json @@ -39,11 +39,12 @@ "leaflet": "^1.4.0", "linkstate": "^1.1.1", "moment": "^2.24.0", - "preact": "^10.3.2", + "preact": "^10.2.1", "preact-cli-plugin-fast-async": "^1.0.1", "preact-i18n": "^2.0.0-preactx.2", "preact-router": "^3.2.1", "qrcode": "^1.4.2", + "react": "^16.13.1", "react-big-calendar": "^0.22.1", "react-datepicker": "^2.13.0", "react-select": "^3.0.8", diff --git a/front/src/config/demo.json b/front/src/config/demo.json index 59bf6fe1c3..82a68b1302 100644 --- a/front/src/config/demo.json +++ b/front/src/config/demo.json @@ -626,7 +626,7 @@ ] } ], - "get /api/v1/service/rflink/device" : [ + "get /api/v1/service/rflink/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", "service_id": "a810b8db-6d04-4697-bed3-c4b72c996279", @@ -647,7 +647,7 @@ } ] } - ], + ], "get /api/v1/service/zwave/device": [ { "id": "fbedb47f-4d25-4381-8923-2633b23192a0", diff --git a/server/services/rflink/package-lock.json b/server/services/rflink/package-lock.json index fab23c89c5..7079861d1c 100644 --- a/server/services/rflink/package-lock.json +++ b/server/services/rflink/package-lock.json @@ -1,553 +1,553 @@ { - "name": "gladys-rflink", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@serialport/binding-abstract": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-8.0.6.tgz", - "integrity": "sha512-1swwUVoRyQ9ubxrkJ8JPppykohUpTAP4jkGr36e9NjbVocSPfqeX6tFZFwl/IdUlwJwxGdbKDqq7FvXniCQUMw==", - "requires": { - "debug": "^4.1.1" - } - }, - "@serialport/binding-mock": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-8.0.6.tgz", - "integrity": "sha512-BIbY5/PsDDo0QWDNCCxDgpowAdks+aZR8BOsEtK2GoASTTcJCy1fBwPIfH870o7rnbH901wY3C+yuTfdOvSO9A==", - "requires": { - "@serialport/binding-abstract": "^8.0.6", - "debug": "^4.1.1" - } - }, - "@serialport/bindings": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-8.0.7.tgz", - "integrity": "sha512-IqudDL8ne2Y2S0W5fKA6wdgHCIA2e2OIaPVYhGy6duE6legNHFY+05CLicHAyAeTocXmHU7rVNxzVQrOG5tM4g==", - "requires": { - "@serialport/binding-abstract": "^8.0.6", - "@serialport/parser-readline": "^8.0.6", - "bindings": "^1.5.0", - "debug": "^4.1.1", - "nan": "^2.14.0", - "prebuild-install": "^5.3.0" - } - }, - "@serialport/parser-byte-length": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-8.0.6.tgz", - "integrity": "sha512-92mrFxFEvq3gRvSM7ANK/jfbmHslz91a5oYJy/nbSn4H/MCRXjxR2YOkQgVXuN+zLt+iyDoW3pcOP4Sc1nWdqQ==" - }, - "@serialport/parser-cctalk": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-8.0.6.tgz", - "integrity": "sha512-pqtCYQPgxnxHygiXUPCfgX7sEx+fdR/ObjpscidynEULUq2fFrC5kBkrxRbTfHRtTaU2ii9DyjFq0JVRCbhI0Q==" - }, - "@serialport/parser-delimiter": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-8.0.6.tgz", - "integrity": "sha512-ogKOcPisPMlVtirkuDu3SFTF0+xT0ijxoH7XjpZiYL41EVi367MwuCnEmXG+dEKKnF0j9EPqOyD2LGSJxaFmhQ==" - }, - "@serialport/parser-readline": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-8.0.6.tgz", - "integrity": "sha512-OYBT2mpczh9QUI3MTw8j0A0tIlPVjpVipvuVnjRkYwxrxPeq04RaLFhaDpuRzua5rTKMt89c1y3btYeoDXMjAA==", - "requires": { - "@serialport/parser-delimiter": "^8.0.6" - } - }, - "@serialport/parser-ready": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-8.0.6.tgz", - "integrity": "sha512-xcEqv4rc119WR5JzAuu8UeJOlAwET2PTdNb6aIrrLlmTxhvuBbuRFcsnF3BpH9jUL30Kh7a6QiNXIwVG+WR/1Q==" - }, - "@serialport/parser-regex": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-8.0.6.tgz", - "integrity": "sha512-J8KY75Azz5ZyExmyM5YfUxbXOWBkZCytKgCCmZ966ttwZS0bUZOuoCaZj2Zp4VILJAiLuxHoqc0foi67Fri5+g==" - }, - "@serialport/stream": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-8.0.6.tgz", - "integrity": "sha512-ym1PwM0rwLrj90vRBB66I1hwMXbuMw9wGTxqns75U3N/tuNFOH85mxXaYVF2TpI66aM849NoI1jMm50fl9equg==", - "requires": { - "debug": "^4.1.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bl": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", - "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.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" - } - } - } - }, - "buffer": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", - "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" - } - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" - }, - "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==" - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", - "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" - }, - "napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "node-abi": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", - "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", - "requires": { - "semver": "^5.4.1" - } - }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "prebuild-install": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", - "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^3.0.3", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, + "name": "gladys-rflink", + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@serialport/binding-abstract": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/binding-abstract/-/binding-abstract-8.0.6.tgz", + "integrity": "sha512-1swwUVoRyQ9ubxrkJ8JPppykohUpTAP4jkGr36e9NjbVocSPfqeX6tFZFwl/IdUlwJwxGdbKDqq7FvXniCQUMw==", + "requires": { + "debug": "^4.1.1" + } + }, + "@serialport/binding-mock": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-8.0.6.tgz", + "integrity": "sha512-BIbY5/PsDDo0QWDNCCxDgpowAdks+aZR8BOsEtK2GoASTTcJCy1fBwPIfH870o7rnbH901wY3C+yuTfdOvSO9A==", + "requires": { + "@serialport/binding-abstract": "^8.0.6", + "debug": "^4.1.1" + } + }, + "@serialport/bindings": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@serialport/bindings/-/bindings-8.0.7.tgz", + "integrity": "sha512-IqudDL8ne2Y2S0W5fKA6wdgHCIA2e2OIaPVYhGy6duE6legNHFY+05CLicHAyAeTocXmHU7rVNxzVQrOG5tM4g==", + "requires": { + "@serialport/binding-abstract": "^8.0.6", + "@serialport/parser-readline": "^8.0.6", + "bindings": "^1.5.0", + "debug": "^4.1.1", + "nan": "^2.14.0", + "prebuild-install": "^5.3.0" + } + }, + "@serialport/parser-byte-length": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-8.0.6.tgz", + "integrity": "sha512-92mrFxFEvq3gRvSM7ANK/jfbmHslz91a5oYJy/nbSn4H/MCRXjxR2YOkQgVXuN+zLt+iyDoW3pcOP4Sc1nWdqQ==" + }, + "@serialport/parser-cctalk": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-8.0.6.tgz", + "integrity": "sha512-pqtCYQPgxnxHygiXUPCfgX7sEx+fdR/ObjpscidynEULUq2fFrC5kBkrxRbTfHRtTaU2ii9DyjFq0JVRCbhI0Q==" + }, + "@serialport/parser-delimiter": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-8.0.6.tgz", + "integrity": "sha512-ogKOcPisPMlVtirkuDu3SFTF0+xT0ijxoH7XjpZiYL41EVi367MwuCnEmXG+dEKKnF0j9EPqOyD2LGSJxaFmhQ==" + }, + "@serialport/parser-readline": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-8.0.6.tgz", + "integrity": "sha512-OYBT2mpczh9QUI3MTw8j0A0tIlPVjpVipvuVnjRkYwxrxPeq04RaLFhaDpuRzua5rTKMt89c1y3btYeoDXMjAA==", + "requires": { + "@serialport/parser-delimiter": "^8.0.6" + } + }, + "@serialport/parser-ready": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-8.0.6.tgz", + "integrity": "sha512-xcEqv4rc119WR5JzAuu8UeJOlAwET2PTdNb6aIrrLlmTxhvuBbuRFcsnF3BpH9jUL30Kh7a6QiNXIwVG+WR/1Q==" + }, + "@serialport/parser-regex": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-8.0.6.tgz", + "integrity": "sha512-J8KY75Azz5ZyExmyM5YfUxbXOWBkZCytKgCCmZ966ttwZS0bUZOuoCaZj2Zp4VILJAiLuxHoqc0foi67Fri5+g==" + }, + "@serialport/stream": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-8.0.6.tgz", + "integrity": "sha512-ym1PwM0rwLrj90vRBB66I1hwMXbuMw9wGTxqns75U3N/tuNFOH85mxXaYVF2TpI66aM849NoI1jMm50fl9equg==", + "requires": { + "debug": "^4.1.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.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" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "serialport": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-8.0.6.tgz", - "integrity": "sha512-TDdP374kZ4+iYKqZbx47eSnJEMoQQfZwztAikWFjZORU1HokOSc3KVuyACy1yX2cVK6u+3C7PmBK3N31CLxMWg==", - "requires": { - "@serialport/binding-mock": "^8.0.6", - "@serialport/bindings": "^8.0.6", - "@serialport/parser-byte-length": "^8.0.6", - "@serialport/parser-cctalk": "^8.0.6", - "@serialport/parser-delimiter": "^8.0.6", - "@serialport/parser-readline": "^8.0.6", - "@serialport/parser-ready": "^8.0.6", - "@serialport/parser-regex": "^8.0.6", - "@serialport/stream": "^8.0.6", - "debug": "^4.1.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" - }, - "simple-get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", - "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", - "requires": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "tar-fs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", - "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", - "requires": { - "chownr": "^1.1.1", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "tar-stream": "^2.0.0" - } - }, - "tar-stream": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", - "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", - "requires": { - "bl": "^4.0.1", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "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" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "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" + } } + } + }, + "buffer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", + "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" + }, + "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==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node-abi": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.15.0.tgz", + "integrity": "sha512-FeLpTS0F39U7hHZU1srAK4Vx+5AHNVOTP+hxBNQknR/54laTHSFIJkDWDqiquY1LeLUgTfPN7sLPhMubx0PLAg==", + "requires": { + "semver": "^5.4.1" + } + }, + "noop-logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "prebuild-install": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz", + "integrity": "sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g==", + "requires": { + "detect-libc": "^1.0.3", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "napi-build-utils": "^1.0.1", + "node-abi": "^2.7.0", + "noop-logger": "^0.1.1", + "npmlog": "^4.0.1", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0", + "which-pm-runs": "^1.0.0" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "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" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "serialport": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-8.0.6.tgz", + "integrity": "sha512-TDdP374kZ4+iYKqZbx47eSnJEMoQQfZwztAikWFjZORU1HokOSc3KVuyACy1yX2cVK6u+3C7PmBK3N31CLxMWg==", + "requires": { + "@serialport/binding-mock": "^8.0.6", + "@serialport/bindings": "^8.0.6", + "@serialport/parser-byte-length": "^8.0.6", + "@serialport/parser-cctalk": "^8.0.6", + "@serialport/parser-delimiter": "^8.0.6", + "@serialport/parser-readline": "^8.0.6", + "@serialport/parser-ready": "^8.0.6", + "@serialport/parser-regex": "^8.0.6", + "@serialport/stream": "^8.0.6", + "debug": "^4.1.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", + "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.0.tgz", + "integrity": "sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA==", + "requires": { + "chownr": "^1.1.1", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "requires": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "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" + } + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" } + } } From 99c63d8a7bb7574703f328dfc5b8fd94b3cfa92e Mon Sep 17 00:00:00 2001 From: Mathis Tondenier Date: Mon, 23 Mar 2020 08:26:03 +0100 Subject: [PATCH 077/236] eslint --- front/package-lock.json | 10 ---------- front/package.json | 3 +-- front/src/actions/gatewayLinkUser.js | 1 - front/src/actions/integration.js | 1 - front/src/actions/login/loginGateway.js | 1 - front/src/actions/main.js | 1 - front/src/actions/profile.js | 1 - front/src/components/device/SelectDeviceFeature.jsx | 1 - front/src/components/device/index.js | 1 - front/src/routes/calendar/index.js | 1 - front/src/routes/gateway-forgot-password/index.js | 1 - front/src/routes/gateway-setup/index.js | 1 - .../all/rflink/device-page/setup/Feature.jsx | 2 -- .../integration/all/sonoff/device-page/SonoffBox.jsx | 4 ---- .../scene/edit-scene/actions/SendMessageParams.jsx | 1 - .../scene/edit-scene/actions/TurnOnOffLightParams.jsx | 1 - front/src/routes/scene/edit-scene/index.js | 1 - front/src/routes/signup-gateway/index.js | 1 - 18 files changed, 1 insertion(+), 32 deletions(-) diff --git a/front/package-lock.json b/front/package-lock.json index 6505bea43a..be25fc85ca 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -14156,16 +14156,6 @@ } } }, - "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - } - }, "react-big-calendar": { "version": "0.22.1", "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.22.1.tgz", diff --git a/front/package.json b/front/package.json index ffe652dc8d..de1e4eb6c6 100644 --- a/front/package.json +++ b/front/package.json @@ -39,12 +39,11 @@ "leaflet": "^1.4.0", "linkstate": "^1.1.1", "moment": "^2.24.0", - "preact": "^10.2.1", + "preact": "^10.3.2", "preact-cli-plugin-fast-async": "^1.0.1", "preact-i18n": "^2.0.0-preactx.2", "preact-router": "^3.2.1", "qrcode": "^1.4.2", - "react": "^16.13.1", "react-big-calendar": "^0.22.1", "react-datepicker": "^2.13.0", "react-select": "^3.0.8", diff --git a/front/src/actions/gatewayLinkUser.js b/front/src/actions/gatewayLinkUser.js index 58a6ae19c6..0e071b3a91 100644 --- a/front/src/actions/gatewayLinkUser.js +++ b/front/src/actions/gatewayLinkUser.js @@ -14,7 +14,6 @@ function createActions(store) { usersGetStatus: RequestStatus.Success }); } catch (e) { - console.log(e); const error = get(e, 'response.error'); const errorMessage = get(e, 'response.error_message'); const errorMessageOtherFormat = get(e, 'response.message'); diff --git a/front/src/actions/integration.js b/front/src/actions/integration.js index a3c2ba4572..2279cf357f 100644 --- a/front/src/actions/integration.js +++ b/front/src/actions/integration.js @@ -29,7 +29,6 @@ const actions = store => ({ currentIntegration }); } catch (e) { - console.log(e); } }, getIntegrationByCategory(state, category) { diff --git a/front/src/actions/login/loginGateway.js b/front/src/actions/login/loginGateway.js index dcbbdd63d4..1b1af2a94d 100644 --- a/front/src/actions/login/loginGateway.js +++ b/front/src/actions/login/loginGateway.js @@ -91,7 +91,6 @@ function createActions(store) { route('/link-gateway-user'); } } catch (e) { - console.log(e); const error = get(e, 'response.error'); const errorMessage = get(e, 'response.error_message'); // if user was previously linked to another instance, we reset the user id diff --git a/front/src/actions/main.js b/front/src/actions/main.js index 88c41a4aea..210da00483 100644 --- a/front/src/actions/main.js +++ b/front/src/actions/main.js @@ -59,7 +59,6 @@ function createActions(store) { } else if (error === 'GATEWAY_USER_NOT_LINKED') { route('/link-gateway-user'); } else { - console.log(e); } } }, diff --git a/front/src/actions/profile.js b/front/src/actions/profile.js index 91a6b1268b..b0b00d95cd 100644 --- a/front/src/actions/profile.js +++ b/front/src/actions/profile.js @@ -150,7 +150,6 @@ function createActions(store) { }); actions.getMySelf(state); } catch (e) { - console.log(e); store.setState({ ProfilePatchStatus: RequestStatus.Error }); diff --git a/front/src/components/device/SelectDeviceFeature.jsx b/front/src/components/device/SelectDeviceFeature.jsx index 00ebb92772..f0ed91154b 100644 --- a/front/src/components/device/SelectDeviceFeature.jsx +++ b/front/src/components/device/SelectDeviceFeature.jsx @@ -55,7 +55,6 @@ class SelectDeviceFeature extends Component { } return deviceOptions; } catch (e) { - console.log(e); } }; handleChange = selectedOption => { diff --git a/front/src/components/device/index.js b/front/src/components/device/index.js index b2d5635a22..1fb6e3d3f6 100644 --- a/front/src/components/device/index.js +++ b/front/src/components/device/index.js @@ -167,7 +167,6 @@ class EditDevicePage extends Component { loading: false }); } catch (e) { - console.log(e); } } diff --git a/front/src/routes/calendar/index.js b/front/src/routes/calendar/index.js index cd3e2626c4..82d7cbf0b2 100644 --- a/front/src/routes/calendar/index.js +++ b/front/src/routes/calendar/index.js @@ -12,7 +12,6 @@ const localizer = momentLocalizer(moment); @connect('', actions) class Map extends Component { onSelectSlot(slotInfo) { - console.log(slotInfo); } render({}, {}) { return ( diff --git a/front/src/routes/gateway-forgot-password/index.js b/front/src/routes/gateway-forgot-password/index.js index 566a199b72..ae76cbd130 100644 --- a/front/src/routes/gateway-forgot-password/index.js +++ b/front/src/routes/gateway-forgot-password/index.js @@ -17,7 +17,6 @@ class ForgotPasswordPage extends Component { .forgotPassword(this.state.email) .then(() => this.setState({ success: true, forgotInProgress: false })) .catch(e => { - console.log(e); this.setState({ success: true, forgotInProgress: false }); }); }; diff --git a/front/src/routes/gateway-setup/index.js b/front/src/routes/gateway-setup/index.js index 36a5cdbc90..f689560de4 100644 --- a/front/src/routes/gateway-setup/index.js +++ b/front/src/routes/gateway-setup/index.js @@ -19,7 +19,6 @@ class LinkGatewayUser extends Component { this.setState({ savingUserLoading: false }); route('/dashboard'); } catch (e) { - console.log(e); this.setState({ savingUserLoading: false, error: true }); } }; diff --git a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx index 4795472cbd..0096340274 100644 --- a/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx +++ b/front/src/routes/integration/all/rflink/device-page/setup/Feature.jsx @@ -154,7 +154,6 @@ class RflinkFeatureBoxComponent extends Component { this.props.updateFeatureProperty(e, 'unit', this.props.featureIndex); }; updateSwitchId = e => { - console.log(this.props); this.props.feature.switchId = e.target.value; let external = { target: { @@ -165,7 +164,6 @@ class RflinkFeatureBoxComponent extends Component { this.updateExternalId(external); }; updateSwitchNumber = e => { - console.log(this.props); this.props.feature.switchNumber = e.target.value; let external = { target: { diff --git a/front/src/routes/integration/all/sonoff/device-page/SonoffBox.jsx b/front/src/routes/integration/all/sonoff/device-page/SonoffBox.jsx index dfef67c8f1..9d796606d7 100644 --- a/front/src/routes/integration/all/sonoff/device-page/SonoffBox.jsx +++ b/front/src/routes/integration/all/sonoff/device-page/SonoffBox.jsx @@ -25,12 +25,9 @@ class SonoffBox extends Component { updateTopic = e => { let { value } = e.target; if (!value.startsWith('sonoff:')) { - console.log('dont starts with sonoff:', value); if (value.length < 7) { - console.log('< 7', value); value = 'sonoff:'; } else { - console.log('>= 7', value); value = `sonoff:${value}`; } } @@ -44,7 +41,6 @@ class SonoffBox extends Component { updateModel = e => { const selectedModel = e.target.value; - console.log(GetFeatures(selectedModel)); this.props.updateDeviceField('sonoffDevices', this.props.deviceIndex, 'model', selectedModel); this.props.updateDeviceField('sonoffDevices', this.props.deviceIndex, 'features', GetFeatures(selectedModel)); diff --git a/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx b/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx index fec032ad5f..4bd0a02194 100644 --- a/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx @@ -19,7 +19,6 @@ class SendMessageParams extends Component { this.refreshSelectedOptions(this.props); return userOptions; } catch (e) { - console.log(e); } }; handleChangeText = e => { diff --git a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx index 7d75b96317..6defea3ac4 100644 --- a/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/TurnOnOffLightParams.jsx @@ -21,7 +21,6 @@ class TurnOnOffLight extends Component { this.refreshSelectedOptions(this.props); return deviceOptions; } catch (e) { - console.log(e); } }; handleChange = selectedOptions => { diff --git a/front/src/routes/scene/edit-scene/index.js b/front/src/routes/scene/edit-scene/index.js index 622f73dca2..13d8d6c076 100644 --- a/front/src/routes/scene/edit-scene/index.js +++ b/front/src/routes/scene/edit-scene/index.js @@ -49,7 +49,6 @@ class EditScene extends Component { try { await this.props.httpClient.patch(`/api/v1/scene/${this.props.scene_selector}`, this.state.scene); } catch (e) { - console.log(e); this.setState({ error: true }); } this.setState({ saving: false }); diff --git a/front/src/routes/signup-gateway/index.js b/front/src/routes/signup-gateway/index.js index e07d13acb4..5afc89c0c9 100644 --- a/front/src/routes/signup-gateway/index.js +++ b/front/src/routes/signup-gateway/index.js @@ -100,7 +100,6 @@ class SignupPage extends Component { } else if (error.response && error.response.status === 409) { this.setState({ accountAlreadyExist: true }); } else { - console.log(error); this.setState({ unknownError: true }); } }); From 7102c6ab19a5f7f5ba1ff391b508d3889d42da1b Mon Sep 17 00:00:00 2001 From: Mathis Tondenier Date: Mon, 23 Mar 2020 10:08:56 +0100 Subject: [PATCH 078/236] prettier --- front/package-lock.json | 24 +++++++++---------- front/package.json | 15 ++++++------ front/src/actions/integration.js | 3 +-- front/src/components/device/index.js | 3 +-- front/src/config/i18n/en.json | 36 ---------------------------- front/src/routes/calendar/index.js | 3 +-- server/services/rflink/index.js | 2 +- 7 files changed, 23 insertions(+), 63 deletions(-) diff --git a/front/package-lock.json b/front/package-lock.json index be25fc85ca..726e34d420 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -229,9 +229,9 @@ "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" }, "@gladysassistant/gladys-gateway-js": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@gladysassistant/gladys-gateway-js/-/gladys-gateway-js-3.2.3.tgz", - "integrity": "sha512-GRmwh3e1/7V48PPX9+U1l9yY61NqaZueHdolI7ylXMRf+7LZgr2vb/w5ALZGhqUmazHpLtlhu8m1RLTySgPV2A==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@gladysassistant/gladys-gateway-js/-/gladys-gateway-js-3.2.2.tgz", + "integrity": "sha512-QIff2Lgn4LjG64saMbXFjZqBDsL/Uo7620G9c/yYymMngzzvSJO5rPNfpgJlW48e7Fx35PkAY4hTMTFljOMQ7A==", "requires": { "@ctrlpanel/pbkdf2": "^1.0.0", "array-buffer-to-hex": "^1.0.0", @@ -4230,9 +4230,9 @@ "integrity": "sha512-fawhJU3ajJud093das8L3PSXqDb+LjclKhRTIbVX1xC+QeHtL/30kNTkS8s/lOiKMGMngxKvwEzQhBEmK/KQnQ==" }, "date-fns": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.10.0.tgz", - "integrity": "sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==" + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.11.0.tgz", + "integrity": "sha512-8P1cDi8ebZyDxUyUprBXwidoEtiQAawYPGvpfb+Dg0G6JrQ+VozwOmm91xYC0vAv1+0VmLehEPb+isg4BGUFfA==" }, "dayjs": { "version": "1.8.17", @@ -13317,9 +13317,9 @@ } }, "preact": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.3.4.tgz", - "integrity": "sha512-wMgzs/RGYf0I1PZf8ZFJdyU/3kCcwepJyVYe+N9FGajyQWarMoPrPfrQajcG0psPj6ySYv2cSuLYFCihvV/Qrw==" + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.2.1.tgz", + "integrity": "sha512-BUNvmQcVtNElku7mYHexIiM5JlGNSW2BY9O2t9xk1NqA43O8wbE0cah6PAlvT7PBHvyDRJ5TAj5Fewdi9DqLoQ==" }, "preact-cli": { "version": "2.2.1", @@ -14176,9 +14176,9 @@ } }, "react-datepicker": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.13.0.tgz", - "integrity": "sha512-vUp3tGGT8SwJX5c6hKnhSJFOFjjBgwKQUv9nl7dkhe/viraxrihLikDr/KVDRGYyj/Q1MBvTpOog4fsSly9o9A==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.14.1.tgz", + "integrity": "sha512-8eWHvrjXfKVkt5rERXC6/c/eEdcE2stIsl+QmTO5Efgpacf8MOCyVpBisJLVLDYjVlENczhOcRlIzvraODHKxA==", "requires": { "classnames": "^2.2.6", "date-fns": "^2.0.1", diff --git a/front/package.json b/front/package.json index de1e4eb6c6..1c926bd854 100644 --- a/front/package.json +++ b/front/package.json @@ -4,10 +4,10 @@ "start": "per-env", "start:production": "npm run -s serve", "start:development": "npm run -s dev", - "build": "cross-env NODE_ENV=production preact build --template src/template.html --no-prerender", - "serve": "npm run build && preact serve", - "dev": "preact watch -p 1444 --template src/template.html", - "eslint": "eslint src --ext .json --ext .js --ext .jsx", + "build": "cross-env NODE_ENV=production preact build --no-prerender", + "serve": "preact build && preact serve", + "dev": "preact watch -p 1444", + "eslint": "eslint src --ext .json --ext .js", "prettier-check": "prettier --check '**/*.js' '**/*.jsx' '**/*.json'", "prettier": "prettier --write '**/*.js' '**/*.jsx' '**/*.json'", "test": "jest --coverage" @@ -21,16 +21,15 @@ "identity-obj-proxy": "^3.0.0", "jest": "^21.2.1", "per-env": "^1.0.2", - "preact-cli": "^2.2.1", + "preact-cli": "^2.1.0", "preact-render-spy": "^1.2.1", "prettier": "^1.17.1" }, "dependencies": { - "@gladysassistant/gladys-gateway-js": "^3.2.3", + "@gladysassistant/gladys-gateway-js": "^3.2.1", "axios": "^0.18.0", "classnames": "^2.2.6", "cropperjs": "^1.5.1", - "date-fns": "^2.10.0", "dayjs": "^1.8.14", "debounce": "^1.2.0", "dotenv": "^6.2.0", @@ -39,7 +38,7 @@ "leaflet": "^1.4.0", "linkstate": "^1.1.1", "moment": "^2.24.0", - "preact": "^10.3.2", + "preact": "^10.2.1", "preact-cli-plugin-fast-async": "^1.0.1", "preact-i18n": "^2.0.0-preactx.2", "preact-router": "^3.2.1", diff --git a/front/src/actions/integration.js b/front/src/actions/integration.js index 2279cf357f..992d71546e 100644 --- a/front/src/actions/integration.js +++ b/front/src/actions/integration.js @@ -28,8 +28,7 @@ const actions = store => ({ store.setState({ currentIntegration }); - } catch (e) { - } + } catch (e) {} }, getIntegrationByCategory(state, category) { const userLanguage = getLanguage(state); diff --git a/front/src/components/device/index.js b/front/src/components/device/index.js index 1fb6e3d3f6..9ae3db4c4d 100644 --- a/front/src/components/device/index.js +++ b/front/src/components/device/index.js @@ -166,8 +166,7 @@ class EditDevicePage extends Component { device, loading: false }); - } catch (e) { - } + } catch (e) {} } render(props, state) { diff --git a/front/src/config/i18n/en.json b/front/src/config/i18n/en.json index 302bab5bcb..8aca18666f 100644 --- a/front/src/config/i18n/en.json +++ b/front/src/config/i18n/en.json @@ -628,42 +628,6 @@ "sunday": "Sunday" } } - }, - "triggers": { - "device": { - "new-state": "Device state change" - } - }, - "triggersCard": { - "newState": { - "equal": "equal", - "superior": "superior", - "superiorOrEqual": "superior or equal", - "less": "less", - "lessOrEqual": "less or equal", - "different": "different", - "valuePlaceholder": "Value", - "on": "On", - "off": "Off" - } - }, - "triggers": { - "device": { - "new-state": "Device state change" - } - }, - "triggersCard": { - "newState": { - "equal": "equal", - "superior": "superior", - "superiorOrEqual": "superior or equal", - "less": "less", - "lessOrEqual": "less or equal", - "different": "different", - "valuePlaceholder": "Value", - "on": "On", - "off": "Off" - } } }, "profile": { diff --git a/front/src/routes/calendar/index.js b/front/src/routes/calendar/index.js index 82d7cbf0b2..cc6d7ce3f0 100644 --- a/front/src/routes/calendar/index.js +++ b/front/src/routes/calendar/index.js @@ -11,8 +11,7 @@ const localizer = momentLocalizer(moment); @connect('', actions) class Map extends Component { - onSelectSlot(slotInfo) { - } + onSelectSlot(slotInfo) {} render({}, {}) { return (
diff --git a/server/services/rflink/index.js b/server/services/rflink/index.js index 370fc13e0b..6fc57e04b5 100644 --- a/server/services/rflink/index.js +++ b/server/services/rflink/index.js @@ -14,7 +14,7 @@ module.exports = function RfLink(gladys, serviceId) { async function start() { const RflinkPath = await gladys.variable.getValue('RFLINK_PATH', serviceId); - if (RflinkPath === undefined || !RflinkPath) { + if (!RflinkPath) { throw new ServiceNotConfiguredError('RFLINK_PATH_NOT_FOUND'); } else { logger.log('Starting Rflink service'); From cbb0bbcbc9072d3055591e0186353277e0e41f72 Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Mon, 23 Mar 2020 10:36:28 +0100 Subject: [PATCH 079/236] Add files via upload --- .../components/device/SelectDeviceFeature.jsx | 222 +++++++++--------- 1 file changed, 111 insertions(+), 111 deletions(-) diff --git a/front/src/components/device/SelectDeviceFeature.jsx b/front/src/components/device/SelectDeviceFeature.jsx index f0ed91154b..608138786e 100644 --- a/front/src/components/device/SelectDeviceFeature.jsx +++ b/front/src/components/device/SelectDeviceFeature.jsx @@ -1,111 +1,111 @@ -import { Component } from 'preact'; -import { connect } from 'unistore/preact'; -import Select from 'react-select'; - -import { getDeviceFeatureName } from '../../utils/device'; - -@connect('httpClient', {}) -class SelectDeviceFeature extends Component { - getOptions = async () => { - try { - // we get the rooms with the devices - const rooms = await this.props.httpClient.get('/api/v1/room?expand=devices'); - const deviceOptions = []; - - const deviceDictionnary = {}; - const deviceFeaturesDictionnary = {}; - - // and compose the multi-level options - rooms.forEach(room => { - const roomDeviceFeatures = []; - room.devices.forEach(device => { - device.features.forEach(feature => { - // keep device / deviceFeature in dictionnary - deviceFeaturesDictionnary[feature.selector] = feature; - deviceDictionnary[feature.selector] = device; - - roomDeviceFeatures.push({ - value: feature.selector, - label: getDeviceFeatureName(this.context.intl.dictionary, device, feature) - }); - }); - }); - if (roomDeviceFeatures.length > 0) { - roomDeviceFeatures.sort((a, b) => { - if (a.label < b.label) { - return -1; - } else if (a.label > b.label) { - return 1; - } - return 0; - }); - deviceOptions.push({ - label: room.name, - options: roomDeviceFeatures - }); - } - }); - await this.setState({ deviceOptions, deviceFeaturesDictionnary, deviceDictionnary }); - await this.refreshSelectedOptions(this.props); - if (this.state.selectedOption && this.state.selectedOption.value) { - this.props.onDeviceFeatureChange( - deviceFeaturesDictionnary[this.state.selectedOption.value], - deviceDictionnary[this.state.selectedOption.value] - ); - } - return deviceOptions; - } catch (e) { - } - }; - handleChange = selectedOption => { - const { deviceFeaturesDictionnary, deviceDictionnary } = this.state; - if (selectedOption && selectedOption.value) { - this.props.onDeviceFeatureChange( - deviceFeaturesDictionnary[selectedOption.value], - deviceDictionnary[selectedOption.value] - ); - } else { - this.props.onDeviceFeatureChange(null); - } - }; - refreshSelectedOptions = async nextProps => { - let selectedOption = ''; - if (nextProps.value && this.state.deviceOptions) { - let deviceOption; - let i = 0; - while (i < this.state.deviceOptions.length && deviceOption === undefined) { - deviceOption = this.state.deviceOptions[i].options.find(option => option.value === nextProps.value); - i++; - } - - if (deviceOption) { - selectedOption = deviceOption; - } - } - await this.setState({ selectedOption }); - }; - constructor(props) { - super(props); - this.state = { - deviceOptions: null, - selectedOption: '' - }; - } - - async componentDidMount() { - this.getOptions(); - } - - componentWillReceiveProps(nextProps) { - this.refreshSelectedOptions(nextProps); - } - - render(props, { selectedOption, deviceOptions }) { - if (!deviceOptions) { - return null; - } - return ; + } +} + +export default SelectDeviceFeature; From a5cff15fd5278b7bf113639ea800d0456b8d447b Mon Sep 17 00:00:00 2001 From: Mathis Tondenier <40740136+mTondenier@users.noreply.github.com> Date: Mon, 23 Mar 2020 10:37:17 +0100 Subject: [PATCH 080/236] Add files via upload --- .../edit-scene/actions/SendMessageParams.jsx | 178 +++++++++--------- .../actions/TurnOnOffLightParams.jsx | 160 ++++++++-------- 2 files changed, 169 insertions(+), 169 deletions(-) diff --git a/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx b/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx index 4bd0a02194..439a81925f 100644 --- a/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx +++ b/front/src/routes/scene/edit-scene/actions/SendMessageParams.jsx @@ -1,89 +1,89 @@ -import Select from 'react-select'; -import { Component } from 'preact'; -import { connect } from 'unistore/preact'; -import { Text, Localizer } from 'preact-i18n'; - -@connect('httpClient', {}) -class SendMessageParams extends Component { - getOptions = async () => { - try { - const users = await this.props.httpClient.get('/api/v1/user'); - const userOptions = []; - users.forEach(user => { - userOptions.push({ - label: user.firstname, - value: user.selector - }); - }); - await this.setState({ userOptions }); - this.refreshSelectedOptions(this.props); - return userOptions; - } catch (e) { - } - }; - handleChangeText = e => { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'text', e.target.value); - }; - handleChange = selectedOption => { - if (selectedOption && selectedOption.value) { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'user', selectedOption.value); - } else { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'user', null); - } - }; - refreshSelectedOptions = nextProps => { - let selectedOption = ''; - if (nextProps.action.user && this.state.userOptions) { - const userOption = this.state.userOptions.find(option => option.value === nextProps.action.user); - - if (userOption) { - selectedOption = userOption; - } - } - this.setState({ selectedOption }); - }; - constructor(props) { - super(props); - this.props = props; - this.state = { - selectedOption: '' - }; - } - componentDidMount() { - if (!this.props.unit) { - this.props.updateActionProperty(this.props.columnIndex, this.props.index, 'unit', 'seconds'); - } - this.getOptions(); - } - componentWillReceiveProps(nextProps) { - this.refreshSelectedOptions(nextProps); - } - render(props, { selectedOption, userOptions }) { - return ( -
-
- -