From 9a76a3ba5082cba3b32b36acabbfe30733115941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Hagelb=C3=A4ck?= Date: Wed, 31 Mar 2021 20:04:45 +0200 Subject: [PATCH] Refactor code to use plejd outputs rather than devices as main entity --- plejd/DeviceRegistry.js | 4 +- plejd/PlejdAddon.js | 88 ++++++++++++----------- plejd/PlejdApi.js | 19 ++--- plejd/PlejdBLEHandler.js | 63 +++++++++-------- plejd/PlejdDeviceCommunication.js | 113 ++++++++++++++++-------------- plejd/Scene.js | 18 ++--- plejd/SceneManager.js | 28 ++++---- plejd/SceneStep.js | 4 ++ plejd/types/DeviceRegistry.d.ts | 4 +- plejd/types/PlejdApi.d.ts | 2 +- 10 files changed, 185 insertions(+), 158 deletions(-) diff --git a/plejd/DeviceRegistry.js b/plejd/DeviceRegistry.js index 5ea12ba..d683ac3 100644 --- a/plejd/DeviceRegistry.js +++ b/plejd/DeviceRegistry.js @@ -45,8 +45,8 @@ class DeviceRegistry { this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId] = []; } if ( - outputDevice.roomId !== outputDevice.uniqueId && - !this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId].includes(outputDevice.uniqueId) + outputDevice.roomId !== outputDevice.uniqueId + && !this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId].includes(outputDevice.uniqueId) ) { this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId].push(outputDevice.uniqueId); logger.verbose( diff --git a/plejd/PlejdAddon.js b/plejd/PlejdAddon.js index eb39c50..cdb3d60 100644 --- a/plejd/PlejdAddon.js +++ b/plejd/PlejdAddon.js @@ -65,57 +65,61 @@ class PlejdAddon extends EventEmitter { }); // subscribe to changes from HA - this.mqttClient.on(MqttClient.EVENTS.stateChanged, (device, command) => { - try { - const deviceId = device.id; + this.mqttClient.on( + MqttClient.EVENTS.stateChanged, + /** @param device {import('./types/DeviceRegistry').OutputDevice} */ + (device, command) => { + try { + const { uniqueId } = device; - if (device.typeName === 'Scene') { - // we're triggering a scene, lets do that and jump out. - // since scenes aren't "real" devices. - this.sceneManager.executeScene(device.id); - return; + if (device.typeName === 'Scene') { + // we're triggering a scene, lets do that and jump out. + // since scenes aren't "real" devices. + this.sceneManager.executeScene(uniqueId); + return; + } + + let state = false; + let commandObj = {}; + + if (typeof command === 'string') { + // switch command + state = command === 'ON'; + commandObj = { + state, + }; + + // since the switch doesn't get any updates on whether it's on or not, + // we fake this by directly send the updateState back to HA in order for + // it to change state. + this.mqttClient.updateOutputState(uniqueId, { + state, + }); + } else { + // eslint-disable-next-line prefer-destructuring + state = command.state === 'ON'; + commandObj = command; + } + + if (state) { + this.plejdDeviceCommunication.turnOn(uniqueId, commandObj); + } else { + this.plejdDeviceCommunication.turnOff(uniqueId, commandObj); + } + } catch (err) { + logger.error('Error in MqttClient.stateChanged callback', err); } - - let state = 'OFF'; - let commandObj = {}; - - if (typeof command === 'string') { - // switch command - state = command; - commandObj = { - state, - }; - - // since the switch doesn't get any updates on whether it's on or not, - // we fake this by directly send the updateState back to HA in order for - // it to change state. - this.mqttClient.updateState(deviceId, { - state: state === 'ON' ? 1 : 0, - }); - } else { - // eslint-disable-next-line prefer-destructuring - state = command.state; - commandObj = command; - } - - if (state === 'ON') { - this.plejdDeviceCommunication.turnOn(deviceId, commandObj); - } else { - this.plejdDeviceCommunication.turnOff(deviceId, commandObj); - } - } catch (err) { - logger.error('Error in MqttClient.stateChanged callback', err); - } - }); + }, + ); this.mqttClient.init(); // subscribe to changes from Plejd this.plejdDeviceCommunication.on( PlejdDeviceCommunication.EVENTS.stateChanged, - (deviceId, command) => { + (uniqueOutputId, command) => { try { - this.mqttClient.updateState(deviceId, command); + this.mqttClient.updateOutputState(uniqueOutputId, command); } catch (err) { logger.error('Error in PlejdService.stateChanged callback', err); } diff --git a/plejd/PlejdApi.js b/plejd/PlejdApi.js index d3a485c..01616c0 100644 --- a/plejd/PlejdApi.js +++ b/plejd/PlejdApi.js @@ -76,8 +76,8 @@ class PlejdApi { } } } - this.deviceRegistry.cryptoKey = this.siteDetails.plejdMesh.cryptoKey; + this.deviceRegistry.setApiSite(this.siteDetails); this.getDevices(); } @@ -300,20 +300,22 @@ class PlejdApi { * * `devices` - physical Plejd devices, duplicated for devices with multiple outputs * devices: [{deviceId, title, objectId, ...}, {...}] * * `deviceAddress` - BLE address of each physical device - * deviceAddress: {[deviceId]: bleDeviceId} + * deviceAddress: {[deviceId]: bleDeviceAddress} * * `outputSettings` - lots of info about load settings, also links devices to output index * outputSettings: [{deviceId, output, deviceParseId, ...}] //deviceParseId === objectId above * * `outputAddress`: BLE address of [0] main output and [n] other output (loads) - * outputAddress: {[deviceId]: {[output]: bleDeviceId}} + * outputAddress: {[deviceId]: {[output]: bleDeviceAddress}} * * `inputSettings` - detailed settings for inputs (buttons, RTR-01, ...), scenes triggered, ... * inputSettings: [{deviceId, input, ...}] //deviceParseId === objectId above * * `inputAddress` - Links inputs to what BLE device they control, or 255 for unassigned/scene - * inputAddress: {[deviceId]: {[input]: bleDeviceId}} + * inputAddress: {[deviceId]: {[input]: bleDeviceAddress}} */ _getPlejdDevices() { this.deviceRegistry.clearPlejdDevices(); this.siteDetails.devices.forEach((device) => { + this.deviceRegistry.addPhysicalDevice(device); + const outputSettings = this.siteDetails.outputSettings.find( (x) => x.deviceParseId === device.objectId, ); @@ -328,7 +330,7 @@ class PlejdApi { outputSettings.output, ); - const bleDeviceIndex = this.siteDetails.outputAddress[device.deviceId][ + const bleOutputAddress = this.siteDetails.outputAddress[device.deviceId][ outputSettings.output ]; @@ -343,7 +345,7 @@ class PlejdApi { /** @type {import('types/DeviceRegistry').OutputDevice} */ const outputDevice = { - bleDeviceIndex, + bleOutputAddress, deviceId: device.deviceId, dimmable, hiddenFromRoomList: device.hiddenFromRoomList, @@ -409,7 +411,7 @@ class PlejdApi { /** @type {import('types/DeviceRegistry').OutputDevice} */ const newDevice = { - bleDeviceIndex: roomAddress, + bleOutputAddress: roomAddress, deviceId: null, dimmable, hiddenFromRoomList: false, @@ -431,6 +433,7 @@ class PlejdApi { } _getSceneDevices() { + this.deviceRegistry.clearSceneDevices(); // add scenes as switches const scenes = this.siteDetails.scenes.filter((x) => x.hiddenFromSceneList === false); @@ -438,7 +441,7 @@ class PlejdApi { const sceneNum = this.siteDetails.sceneIndex[scene.sceneId]; /** @type {import('types/DeviceRegistry').OutputDevice} */ const newScene = { - bleDeviceIndex: sceneNum, + bleOutputAddress: sceneNum, deviceId: undefined, dimmable: false, hiddenFromSceneList: scene.hiddenFromSceneList, diff --git a/plejd/PlejdBLEHandler.js b/plejd/PlejdBLEHandler.js index 84f1329..2bb062b 100644 --- a/plejd/PlejdBLEHandler.js +++ b/plejd/PlejdBLEHandler.js @@ -49,6 +49,8 @@ class PlejBLEHandler extends EventEmitter { connectedDevice = null; consecutiveWriteFails; consecutiveReconnectAttempts = 0; + /** @type {import('./DeviceRegistry')} */ + deviceRegistry; discoveryTimeout = null; plejdService = null; pingRef = null; @@ -152,21 +154,21 @@ class PlejBLEHandler extends EventEmitter { logger.info('BLE init done, waiting for devices.'); } - async sendCommand(command, deviceId, data) { + async sendCommand(command, uniqueOutputId, data) { let payload; let brightnessVal; switch (command) { case COMMANDS.TURN_ON: - payload = this._createHexPayload(deviceId, BLE_CMD_STATE_CHANGE, '01'); + payload = this._createHexPayload(uniqueOutputId, BLE_CMD_STATE_CHANGE, '01'); break; case COMMANDS.TURN_OFF: - payload = this._createHexPayload(deviceId, BLE_CMD_STATE_CHANGE, '00'); + payload = this._createHexPayload(uniqueOutputId, BLE_CMD_STATE_CHANGE, '00'); break; case COMMANDS.DIM: // eslint-disable-next-line no-bitwise brightnessVal = (data << 8) | data; payload = this._createHexPayload( - deviceId, + uniqueOutputId, BLE_CMD_DIM2_CHANGE, `01${brightnessVal.toString(16).padStart(4, '0')}`, ); @@ -194,9 +196,9 @@ class PlejBLEHandler extends EventEmitter { plejd.instance = device; const segments = plejd.path.split('/'); - let fixedPlejdPath = segments[segments.length - 1].replace('dev_', ''); - fixedPlejdPath = fixedPlejdPath.replace(/_/g, ''); - plejd.device = this.deviceRegistry.getDeviceBySerialNumber(fixedPlejdPath); + let plejdSerialNumber = segments[segments.length - 1].replace('dev_', ''); + plejdSerialNumber = plejdSerialNumber.replace(/_/g, ''); + plejd.device = this.deviceRegistry.getPhysicalDevice(plejdSerialNumber); if (plejd.device) { logger.debug( @@ -204,7 +206,7 @@ class PlejBLEHandler extends EventEmitter { ); this.bleDevices.push(plejd); } else { - logger.warn(`Device registry does not contain device with serial ${fixedPlejdPath}`); + logger.warn(`Device registry does not contain device with serial ${plejdSerialNumber}`); } } catch (err) { logger.error(`Failed inspecting ${path}. `, err); @@ -796,7 +798,7 @@ class PlejBLEHandler extends EventEmitter { return; } - const deviceId = decoded.readUInt8(0); + const bleOutputAddress = decoded.readUInt8(0); // Bytes 2-3 is Command/Request const cmd = decoded.readUInt16BE(3); @@ -810,38 +812,41 @@ class PlejBLEHandler extends EventEmitter { logger.silly(`Dim: ${dim.toString(16)}, full precision: ${dimFull.toString(16)}`); } - const deviceName = this.deviceRegistry.getDeviceName(deviceId); + const device = this.deviceRegistry.getOutputDeviceByBleOutputAddress(bleOutputAddress); + const deviceName = device ? device.name : 'Unknown'; + const outputUniqueId = device ? device.uniqueId : null; + if (Logger.shouldLog('verbose')) { // decoded.toString() could potentially be expensive logger.verbose(`Raw event received: ${decoded.toString('hex')}`); logger.verbose( - `Decoded: Device ${deviceId}, cmd ${cmd.toString(16)}, state ${state}, dim ${dim}`, + `Decoded: Device ${outputUniqueId}, cmd ${cmd.toString(16)}, state ${state}, dim ${dim}`, ); } let command; let data = {}; if (cmd === BLE_CMD_DIM_CHANGE || cmd === BLE_CMD_DIM2_CHANGE) { - logger.debug(`${deviceName} (${deviceId}) got state+dim update. S: ${state}, D: ${dim}`); + logger.debug( + `${deviceName} (${outputUniqueId}) got state+dim update. S: ${state}, D: ${dim}`, + ); command = COMMANDS.DIM; data = { state, dim }; - this.emit(PlejBLEHandler.EVENTS.commandReceived, deviceId, command, data); + this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data); } else if (cmd === BLE_CMD_STATE_CHANGE) { - logger.debug(`${deviceName} (${deviceId}) got state update. S: ${state}`); + logger.debug(`${deviceName} (${outputUniqueId}) got state update. S: ${state}`); command = state ? COMMANDS.TURN_ON : COMMANDS.TURN_OFF; - this.emit(PlejBLEHandler.EVENTS.commandReceived, deviceId, command, data); + this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data); } else if (cmd === BLE_CMD_SCENE_TRIG) { const sceneId = state; const sceneName = this.deviceRegistry.getSceneName(sceneId); - logger.debug( - `${sceneName} (${sceneId}) scene triggered (device id ${deviceId}). Name can be misleading if there is a device with the same numeric id.`, - ); + logger.debug(`${sceneName} (${sceneId}) scene triggered (device id ${outputUniqueId}).`); command = COMMANDS.TRIGGER_SCENE; data = { sceneId }; - this.emit(PlejBLEHandler.EVENTS.commandReceived, deviceId, command, data); + this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data); } else if (cmd === BLE_CMD_TIME_UPDATE) { const now = new Date(); // Guess Plejd timezone based on HA time zone @@ -851,7 +856,7 @@ class PlejBLEHandler extends EventEmitter { const plejdTimestampUTC = (decoded.readInt32LE(5) + offsetSecondsGuess) * 1000; const diffSeconds = Math.round((plejdTimestampUTC - now.getTime()) / 1000); if ( - deviceId !== BLE_BROADCAST_DEVICE_ID + bleOutputAddress !== BLE_BROADCAST_DEVICE_ID || Logger.shouldLog('verbose') || Math.abs(diffSeconds) > 60 ) { @@ -863,7 +868,7 @@ class PlejBLEHandler extends EventEmitter { logger.warn( `Plejd clock time off by more than 1 minute. Reported time: ${plejdTime.toString()}, diff ${diffSeconds} seconds. Time will be set hourly.`, ); - if (this.connectedDevice && deviceId === this.connectedDevice.id) { + if (this.connectedDevice && bleOutputAddress === this.connectedDevice.id) { // Requested time sync by us const newLocalTimestamp = now.getTime() / 1000 - offsetSecondsGuess; logger.info(`Setting time to ${now.toString()}`); @@ -881,7 +886,7 @@ class PlejBLEHandler extends EventEmitter { ); } } - } else if (deviceId !== BLE_BROADCAST_DEVICE_ID) { + } else if (bleOutputAddress !== BLE_BROADCAST_DEVICE_ID) { logger.info('Got time response. Plejd clock time in sync with Home Assistant time'); } } @@ -889,19 +894,19 @@ class PlejBLEHandler extends EventEmitter { logger.verbose( `Command ${cmd.toString(16)} unknown. ${decoded.toString( 'hex', - )}. Device ${deviceName} (${deviceId})`, + )}. Device ${deviceName} (${bleOutputAddress}: ${outputUniqueId})`, ); } } _createHexPayload( - deviceId, + bleOutputAddress, command, hexDataString, requestResponseCommand = BLE_REQUEST_NO_RESPONSE, ) { return this._createPayload( - deviceId, + bleOutputAddress, command, 5 + Math.ceil(hexDataString.length / 2), (payload) => payload.write(hexDataString, 5, 'hex'), @@ -911,14 +916,14 @@ class PlejBLEHandler extends EventEmitter { // eslint-disable-next-line class-methods-use-this _createPayload( - deviceId, + bleOutputAddress, command, bufferLength, payloadBufferAddDataFunc, requestResponseCommand = BLE_REQUEST_NO_RESPONSE, ) { const payload = Buffer.alloc(bufferLength); - payload.writeUInt8(deviceId); + payload.writeUInt8(bleOutputAddress); payload.writeUInt16BE(requestResponseCommand, 1); payload.writeUInt16BE(command, 3); payloadBufferAddDataFunc(payload); @@ -945,12 +950,12 @@ class PlejBLEHandler extends EventEmitter { let ct = cipher.update(buf).toString('hex'); ct += cipher.final().toString('hex'); - ct = Buffer.from(ct, 'hex'); + const ctBuf = Buffer.from(ct, 'hex'); let output = ''; for (let i = 0, { length } = data; i < length; i++) { // eslint-disable-next-line no-bitwise - output += String.fromCharCode(data[i] ^ ct[i % 16]); + output += String.fromCharCode(data[i] ^ ctBuf[i % 16]); } return Buffer.from(output, 'ascii'); diff --git a/plejd/PlejdDeviceCommunication.js b/plejd/PlejdDeviceCommunication.js index 2fa4e2a..3c4431b 100644 --- a/plejd/PlejdDeviceCommunication.js +++ b/plejd/PlejdDeviceCommunication.js @@ -12,11 +12,13 @@ const MAX_RETRY_COUNT = 10; // Could be made a setting class PlejdDeviceCommunication extends EventEmitter { bleConnected; - bleDeviceTransitionTimers = {}; + bleOutputTransitionTimers = {}; plejdBleHandler; config; /** @type {import('./DeviceRegistry')} */ deviceRegistry; + // eslint-disable-next-line max-len + /** @type {{uniqueOutputId: string, command: string, data: any, shouldRetry: boolean, retryCount?: number}[]} */ writeQueue = []; writeQueueRef = null; @@ -35,7 +37,7 @@ class PlejdDeviceCommunication extends EventEmitter { } cleanup() { - Object.values(this.bleDeviceTransitionTimers).forEach((t) => clearTimeout(t)); + Object.values(this.bleOutputTransitionTimers).forEach((t) => clearTimeout(t)); this.plejdBleHandler.cleanup(); this.plejdBleHandler.removeAllListeners(PlejBLEHandler.EVENTS.commandReceived); this.plejdBleHandler.removeAllListeners(PlejBLEHandler.EVENTS.connected); @@ -47,7 +49,10 @@ class PlejdDeviceCommunication extends EventEmitter { this.cleanup(); this.bleConnected = false; // eslint-disable-next-line max-len - this.plejdBleHandler.on(PlejBLEHandler.EVENTS.commandReceived, (deviceId, command, data) => this._bleCommandReceived(deviceId, command, data)); + this.plejdBleHandler.on( + PlejBLEHandler.EVENTS.commandReceived, + (uniqueOutputId, command, data) => this._bleCommandReceived(uniqueOutputId, command, data), + ); this.plejdBleHandler.on(PlejBLEHandler.EVENTS.connected, () => { logger.info('Bluetooth connected. Plejd BLE up and running!'); @@ -71,42 +76,42 @@ class PlejdDeviceCommunication extends EventEmitter { } } - turnOn(deviceId, command) { - const deviceName = this.deviceRegistry.getOutputDeviceName(deviceId); + turnOn(uniqueOutputId, command) { + const deviceName = this.deviceRegistry.getOutputDeviceName(uniqueOutputId); logger.info( - `Plejd got turn on command for ${deviceName} (${deviceId}), brightness ${command.brightness}${ + `Plejd got turn on command for ${deviceName} (${uniqueOutputId}), brightness ${ + command.brightness + }${command.transition ? `, transition: ${command.transition}` : ''}`, + ); + this._transitionTo(uniqueOutputId, command.brightness, command.transition, deviceName); + } + + turnOff(uniqueOutputId, command) { + const deviceName = this.deviceRegistry.getOutputDeviceName(uniqueOutputId); + logger.info( + `Plejd got turn off command for ${deviceName} (${uniqueOutputId})${ command.transition ? `, transition: ${command.transition}` : '' }`, ); - this._transitionTo(deviceId, command.brightness, command.transition, deviceName); + this._transitionTo(uniqueOutputId, 0, command.transition, deviceName); } - turnOff(deviceId, command) { - const deviceName = this.deviceRegistry.getOutputDeviceName(deviceId); - logger.info( - `Plejd got turn off command for ${deviceName} (${deviceId})${ - command.transition ? `, transition: ${command.transition}` : '' - }`, - ); - this._transitionTo(deviceId, 0, command.transition, deviceName); - } - - _bleCommandReceived(deviceId, command, data) { + _bleCommandReceived(uniqueOutputId, command, data) { try { if (command === COMMANDS.DIM) { - this.deviceRegistry.setOutputState(deviceId, data.state, data.dim); - this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, deviceId, { + this.deviceRegistry.setOutputState(uniqueOutputId, data.state, data.dim); + this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, uniqueOutputId, { state: !!data.state, brightness: data.dim, }); } else if (command === COMMANDS.TURN_ON) { - this.deviceRegistry.setOutputState(deviceId, true); - this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, deviceId, { + this.deviceRegistry.setOutputState(uniqueOutputId, true); + this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, uniqueOutputId, { state: 1, }); } else if (command === COMMANDS.TURN_OFF) { - this.deviceRegistry.setOutputState(deviceId, false); - this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, deviceId, { + this.deviceRegistry.setOutputState(uniqueOutputId, false); + this.emit(PlejdDeviceCommunication.EVENTS.stateChanged, uniqueOutputId, { state: 0, }); } else if (command === COMMANDS.TRIGGER_SCENE) { @@ -119,18 +124,18 @@ class PlejdDeviceCommunication extends EventEmitter { } } - _clearDeviceTransitionTimer(deviceId) { - if (this.bleDeviceTransitionTimers[deviceId]) { - clearInterval(this.bleDeviceTransitionTimers[deviceId]); + _clearDeviceTransitionTimer(uniqueOutputId) { + if (this.bleOutputTransitionTimers[uniqueOutputId]) { + clearInterval(this.bleOutputTransitionTimers[uniqueOutputId]); } } - _transitionTo(deviceId, targetBrightness, transition, deviceName) { - const device = this.deviceRegistry.getOutputDevice(deviceId); + _transitionTo(uniqueOutputId, targetBrightness, transition, deviceName) { + const device = this.deviceRegistry.getOutputDevice(uniqueOutputId); const initialBrightness = device ? device.state && device.dim : null; - this._clearDeviceTransitionTimer(deviceId); + this._clearDeviceTransitionTimer(uniqueOutputId); - const isDimmable = this.deviceRegistry.getOutputDevice(deviceId).dimmable; + const isDimmable = this.deviceRegistry.getOutputDevice(uniqueOutputId).dimmable; if ( transition > 1 @@ -165,7 +170,7 @@ class PlejdDeviceCommunication extends EventEmitter { let nSteps = 0; - this.bleDeviceTransitionTimers[deviceId] = setInterval(() => { + this.bleOutputTransitionTimers[uniqueOutputId] = setInterval(() => { const tElapsedMs = new Date().getTime() - dtStart.getTime(); let tElapsed = tElapsedMs / 1000; @@ -179,20 +184,20 @@ class PlejdDeviceCommunication extends EventEmitter { if (tElapsed === transition) { nSteps++; - this._clearDeviceTransitionTimer(deviceId); + this._clearDeviceTransitionTimer(uniqueOutputId); newBrightness = targetBrightness; logger.debug( - `Queueing finalize ${deviceName} (${deviceId}) transition from ${initialBrightness} to ${targetBrightness} in ${tElapsedMs}ms. Done steps ${nSteps}. Average interval ${ + `Queueing finalize ${deviceName} (${uniqueOutputId}) transition from ${initialBrightness} to ${targetBrightness} in ${tElapsedMs}ms. Done steps ${nSteps}. Average interval ${ tElapsedMs / (nSteps || 1) } ms.`, ); - this._setBrightness(deviceId, newBrightness, true, deviceName); + this._setBrightness(uniqueOutputId, newBrightness, true, deviceName); } else { nSteps++; logger.verbose( - `Queueing dim transition for ${deviceName} (${deviceId}) to ${newBrightness}. Total queue length ${this.writeQueue.length}`, + `Queueing dim transition for ${deviceName} (${uniqueOutputId}) to ${newBrightness}. Total queue length ${this.writeQueue.length}`, ); - this._setBrightness(deviceId, newBrightness, false, deviceName); + this._setBrightness(uniqueOutputId, newBrightness, false, deviceName); } }, transitionInterval); } else { @@ -201,34 +206,34 @@ class PlejdDeviceCommunication extends EventEmitter { `Could not transition light change. Either initial value is unknown or change is too small. Requested from ${initialBrightness} to ${targetBrightness}`, ); } - this._setBrightness(deviceId, targetBrightness, true, deviceName); + this._setBrightness(uniqueOutputId, targetBrightness, true, deviceName); } } - _setBrightness(deviceId, brightness, shouldRetry, deviceName) { + _setBrightness(unqiueOutputId, brightness, shouldRetry, deviceName) { if (!brightness && brightness !== 0) { logger.debug( - `Queueing turn on ${deviceName} (${deviceId}). No brightness specified, setting DIM to previous.`, + `Queueing turn on ${deviceName} (${unqiueOutputId}). No brightness specified, setting DIM to previous.`, ); - this._appendCommandToWriteQueue(deviceId, COMMANDS.TURN_ON, null, shouldRetry); + this._appendCommandToWriteQueue(unqiueOutputId, COMMANDS.TURN_ON, null, shouldRetry); } else if (brightness <= 0) { - logger.debug(`Queueing turn off ${deviceId}`); - this._appendCommandToWriteQueue(deviceId, COMMANDS.TURN_OFF, null, shouldRetry); + logger.debug(`Queueing turn off ${unqiueOutputId}`); + this._appendCommandToWriteQueue(unqiueOutputId, COMMANDS.TURN_OFF, null, shouldRetry); } else { if (brightness > 255) { // eslint-disable-next-line no-param-reassign brightness = 255; } - logger.debug(`Queueing ${deviceId} set brightness to ${brightness}`); + logger.debug(`Queueing ${unqiueOutputId} set brightness to ${brightness}`); // eslint-disable-next-line no-bitwise - this._appendCommandToWriteQueue(deviceId, COMMANDS.DIM, brightness, shouldRetry); + this._appendCommandToWriteQueue(unqiueOutputId, COMMANDS.DIM, brightness, shouldRetry); } } - _appendCommandToWriteQueue(deviceId, command, data, shouldRetry) { + _appendCommandToWriteQueue(uniqueOutputId, command, data, shouldRetry) { this.writeQueue.unshift({ - deviceId, + uniqueOutputId, command, data, shouldRetry, @@ -250,28 +255,28 @@ class PlejdDeviceCommunication extends EventEmitter { return; } const queueItem = this.writeQueue.pop(); - const deviceName = this.deviceRegistry.getOutputDeviceName(queueItem.deviceId); + const deviceName = this.deviceRegistry.getOutputDeviceName(queueItem.uniqueOutputId); logger.debug( - `Write queue: Processing ${deviceName} (${queueItem.deviceId}). Command ${ + `Write queue: Processing ${deviceName} (${queueItem.uniqueOutputId}). Command ${ queueItem.command }${queueItem.data ? ` ${queueItem.data}` : ''}. Total queue length: ${ this.writeQueue.length }`, ); - if (this.writeQueue.some((item) => item.deviceId === queueItem.deviceId)) { + if (this.writeQueue.some((item) => item.uniqueOutputId === queueItem.uniqueOutputId)) { logger.verbose( - `Skipping ${deviceName} (${queueItem.deviceId}) ` + `Skipping ${deviceName} (${queueItem.uniqueOutputId}) ` + `${queueItem.command} due to more recent command in queue.`, ); - // Skip commands if new ones exist for the same deviceId + // Skip commands if new ones exist for the same uniqueOutputId // still process all messages in order } else { /* eslint-disable no-await-in-loop */ try { await this.plejdBleHandler.sendCommand( queueItem.command, - queueItem.deviceId, + queueItem.uniqueOutputId, queueItem.data, ); } catch (err) { @@ -282,7 +287,7 @@ class PlejdDeviceCommunication extends EventEmitter { this.writeQueue.push(queueItem); // Add back to top of queue to be processed next; } else { logger.error( - `Write queue: Exceeed max retry count (${MAX_RETRY_COUNT}) for ${deviceName} (${queueItem.deviceId}). Command ${queueItem.command} failed.`, + `Write queue: Exceeed max retry count (${MAX_RETRY_COUNT}) for ${deviceName} (${queueItem.uniqueOutputId}). Command ${queueItem.command} failed.`, ); break; } diff --git a/plejd/Scene.js b/plejd/Scene.js index e0bff71..b22f304 100644 --- a/plejd/Scene.js +++ b/plejd/Scene.js @@ -1,18 +1,20 @@ const SceneStep = require('./SceneStep'); class Scene { - constructor(idx, scene, steps) { + /** + * @param {import('./DeviceRegistry')} deviceRegistry + * @param {number} idx + * @param {import("./types/ApiSite").Scene} scene + */ + constructor(deviceRegistry, idx, scene) { this.id = idx; this.title = scene.title; this.sceneId = scene.sceneId; - const sceneSteps = steps.filter((x) => x.sceneId === scene.sceneId); - this.steps = []; - - // eslint-disable-next-line no-restricted-syntax - for (const step of sceneSteps) { - this.steps.push(new SceneStep(step)); - } + this.steps = deviceRegistry + .getApiSite() + .sceneSteps.filter((step) => step.sceneId === scene.sceneId) + .map((step) => new SceneStep(step)); } } diff --git a/plejd/SceneManager.js b/plejd/SceneManager.js index e5047cc..4f518ad 100644 --- a/plejd/SceneManager.js +++ b/plejd/SceneManager.js @@ -3,25 +3,28 @@ const Scene = require('./Scene'); const logger = Logger.getLogger('scene-manager'); class SceneManager { + /** @private @type {import('./DeviceRegistry')} */ deviceRegistry; - plejdBle; + /** @private @type {import('./PlejdDeviceCommunication')} */ + plejdDeviceCommunication; + /** @private @type {Object.} */ scenes; - constructor(deviceRegistry, plejdBle) { + constructor(deviceRegistry, plejdDeviceCommunication) { this.deviceRegistry = deviceRegistry; - this.plejdBle = plejdBle; + this.plejdDeviceCommunication = plejdDeviceCommunication; this.scenes = {}; } init() { - const scenes = this.deviceRegistry.apiSite.scenes.filter( - (x) => x.hiddenFromSceneList === false, - ); + const scenes = this.deviceRegistry + .getApiSite() + .scenes.filter((x) => x.hiddenFromSceneList === false); this.scenes = {}; scenes.forEach((scene) => { - const idx = this.deviceRegistry.apiSite.sceneIndex[scene.sceneId]; - this.scenes[idx] = new Scene(idx, scene, this.deviceRegistry.apiSite.sceneSteps); + const idx = this.deviceRegistry.getApiSite().sceneIndex[scene.sceneId]; + this.scenes[idx] = new Scene(this.deviceRegistry, idx, scene); }); } @@ -34,14 +37,15 @@ class SceneManager { } scene.steps.forEach((step) => { - const device = this.deviceRegistry.getDeviceBySerialNumber(step.deviceId); + const uniqueId = this.deviceRegistry.getUniqueOutputId(step.deviceId, step.output); + const device = this.deviceRegistry.getOutputDevice(uniqueId); if (device) { if (device.dimmable && step.state) { - this.plejdBle.turnOn(device.id, { brightness: step.brightness }); + this.plejdDeviceCommunication.turnOn(uniqueId, { brightness: step.brightness }); } else if (!device.dimmable && step.state) { - this.plejdBle.turnOn(device.id, {}); + this.plejdDeviceCommunication.turnOn(uniqueId, {}); } else if (!step.state) { - this.plejdBle.turnOff(device.id, {}); + this.plejdDeviceCommunication.turnOff(uniqueId, {}); } } }); diff --git a/plejd/SceneStep.js b/plejd/SceneStep.js index 6456997..3ccc06f 100644 --- a/plejd/SceneStep.js +++ b/plejd/SceneStep.js @@ -1,7 +1,11 @@ class SceneStep { + /** + * @param {import("./types/ApiSite").SceneStep} step + */ constructor(step) { this.sceneId = step.sceneId; this.deviceId = step.deviceId; + this.output = step.output; this.state = step.state === 'On' ? 1 : 0; this.brightness = step.value; } diff --git a/plejd/types/DeviceRegistry.d.ts b/plejd/types/DeviceRegistry.d.ts index 4dc373f..b93c509 100644 --- a/plejd/types/DeviceRegistry.d.ts +++ b/plejd/types/DeviceRegistry.d.ts @@ -3,7 +3,7 @@ export type OutputDevices = { [deviceIdAndOutput: string]: OutputDevice }; export interface OutputDevice { - bleDeviceIndex: number; + bleOutputAddress: number; deviceId: string; dim?: number; dimmable: boolean; @@ -13,7 +13,7 @@ export interface OutputDevice { name: string; output: number; roomId: string; - state: number | undefined; + state: boolean | undefined; type: string; typeName: string; version: string; diff --git a/plejd/types/PlejdApi.d.ts b/plejd/types/PlejdApi.d.ts index 1cbdbe8..3376b65 100644 --- a/plejd/types/PlejdApi.d.ts +++ b/plejd/types/PlejdApi.d.ts @@ -1,6 +1,6 @@ /* eslint-disable no-use-before-define */ -import { ApiSite } from './ApiSite'; +import { ApiSite } from './ApiSite.d.ts'; export type PlejdApi = { config: any;