diff --git a/plejd/.eslintrc.js b/plejd/.eslintrc.js index fc15347..c70b332 100644 --- a/plejd/.eslintrc.js +++ b/plejd/.eslintrc.js @@ -17,7 +17,10 @@ module.exports = { // 'prettier', // 'plugin:prettier/recommended' ], - parser: 'babel-eslint', + parser: '@babel/eslint-parser', + parserOptions: { + requireConfigFile: false, + }, // plugins: ['prettier'], rules: getRules(), }; diff --git a/plejd/Dockerfile b/plejd/Dockerfile index 89cef69..2b9213f 100644 --- a/plejd/Dockerfile +++ b/plejd/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=hassioaddons/base:8.0.6 +ARG BUILD_FROM=hassioaddons/base:14.2.2 FROM $BUILD_FROM ENV LANG C.UTF-8 diff --git a/plejd/MqttClient.js b/plejd/MqttClient.js index 3517041..e17c8f6 100644 --- a/plejd/MqttClient.js +++ b/plejd/MqttClient.js @@ -4,7 +4,7 @@ const mqtt = require('mqtt'); const Configuration = require('./Configuration'); const Logger = require('./Logger'); -const startTopics = ['hass/status', 'homeassistant/status']; +// const startTopics = ['hass/status', 'homeassistant/status']; const logger = Logger.getLogger('plejd-mqtt'); @@ -24,7 +24,7 @@ const TOPIC_TYPES = { CONFIG: 'config', STATE: 'state', AVAILABILITY: 'availability', - COMMAND: 'set', + SET: 'set', }; const getBaseTopic = (/** @type { string } */ uniqueId, /** @type { string } */ mqttDeviceType) => @@ -43,9 +43,8 @@ const getSceneEventTopic = (/** @type {string} */ sceneId) => `${getTopicName(getTriggerUniqueId(sceneId), MQTT_TYPES.DEVICE_AUTOMATION, TOPIC_TYPES.STATE)}`; const getSubscribePath = () => `${discoveryPrefix}/+/${nodeId}/#`; -const decodeTopicRegexp = new RegExp( - /(?[^[]+)\/(?.+)\/plejd\/(?.+)\/(?config|state|availability|set|scene)/, -); +const decodeTopicRegexp = + /(?[^[]+)\/(?.+)\/plejd\/(?.+)\/(?config|state|availability|set|scene)/; const decodeTopic = (topic) => { const matches = decodeTopicRegexp.exec(topic); @@ -62,11 +61,11 @@ const getOutputDeviceDiscoveryPayload = ( unique_id: device.uniqueId, '~': getBaseTopic(device.uniqueId, device.type), state_topic: `~/${TOPIC_TYPES.STATE}`, - command_topic: `~/${TOPIC_TYPES.COMMAND}`, + command_topic: `~/${TOPIC_TYPES.SET}`, availability_topic: `~/${TOPIC_TYPES.AVAILABILITY}`, optimistic: false, qos: 1, - retain: true, + retain: false, // State update messages from HA should not be retained device: { identifiers: `${device.uniqueId}`, manufacturer: 'Plejd', @@ -76,6 +75,15 @@ const getOutputDeviceDiscoveryPayload = ( sw_version: device.version, }, ...(device.type === MQTT_TYPES.LIGHT ? { brightness: device.dimmable, schema: 'json' } : {}), + ...(device.type === MQTT_TYPES.LIGHT && + device.colorTempSettings && + device.colorTempSettings.behavior === 'adjustable' + ? { + min_mireds: 1000000 / device.colorTempSettings.minTemperatureLimit, + max_mireds: 1000000 / device.colorTempSettings.maxTemperatureLimit, + supported_color_modes: ['color_temp'], + } + : {}), }); const getSceneDiscoveryPayload = ( @@ -84,11 +92,11 @@ const getSceneDiscoveryPayload = ( name: sceneDevice.name, unique_id: sceneDevice.uniqueId, '~': getBaseTopic(sceneDevice.uniqueId, MQTT_TYPES.SCENE), - command_topic: `~/${TOPIC_TYPES.COMMAND}`, + command_topic: `~/${TOPIC_TYPES.SET}`, availability_topic: `~/${TOPIC_TYPES.AVAILABILITY}`, payload_on: 'ON', qos: 1, - retain: false, + retain: false, // State update messages from HA should not be retained }); const getInputDeviceTriggerDiscoveryPayload = ( @@ -98,9 +106,10 @@ const getInputDeviceTriggerDiscoveryPayload = ( payload: `${inputDevice.input}`, '~': getBaseTopic(inputDevice.deviceId, MQTT_TYPES.DEVICE_AUTOMATION), qos: 1, + retain: true, // Discovery messages should be retained to account for HA restarts + subtype: `button_${inputDevice.input + 1}`, topic: `~/${TOPIC_TYPES.STATE}`, type: 'button_short_press', - subtype: `button_${inputDevice.input + 1}`, device: { identifiers: `${inputDevice.deviceId}`, manufacturer: 'Plejd', @@ -115,6 +124,7 @@ const getSceneDeviceTriggerhDiscoveryPayload = ( automation_type: 'trigger', '~': getBaseTopic(`${sceneDevice.uniqueId}_trig`, MQTT_TYPES.DEVICE_AUTOMATION), qos: 1, + retain: true, // Discovery messages should be retained to account for HA restarts topic: `~/${TOPIC_TYPES.STATE}`, type: 'scene', subtype: 'trigger', @@ -127,7 +137,7 @@ const getSceneDeviceTriggerhDiscoveryPayload = ( }); const getMqttStateString = (/** @type {boolean} */ state) => (state ? 'ON' : 'OFF'); -const AVAILABLILITY = { ONLINE: 'online', OFFLINE: 'offline' }; +const AVAILABLITY = { ONLINE: 'online', OFFLINE: 'offline' }; class MqttClient extends EventEmitter { /** @type {import('DeviceRegistry')} */ @@ -152,9 +162,13 @@ class MqttClient extends EventEmitter { logger.info('Initializing MQTT connection for Plejd addon'); this.client = mqtt.connect(this.config.mqttBroker, { + clean: true, // We're moving to not saving mqtt messages clientId: `hassio-plejd_${Math.random().toString(16).substr(2, 8)}`, password: this.config.mqttPassword, - protocolVersion: 4, // v5 not supported by HassIO Mosquitto + properties: { + sessionExpiryInterval: 120, // 2 minutes sessions for the QoS, after that old messages are discarded + }, + protocolVersion: 5, queueQoSZero: true, username: this.config.mqttUsername, }); @@ -166,28 +180,25 @@ class MqttClient extends EventEmitter { this.client.on('connect', () => { logger.info('Connected to MQTT.'); - this.client.subscribe( - startTopics, - // Add below when mqtt v5 is supported in Mosquitto 1.6 or 2.0 and forward - // { - // qos: 1, - // nl: true, // don't echo back messages sent - // rap: true, // retain as published - don't force retain = 0 - // }, - (err) => { - if (err) { - logger.error('Unable to subscribe to status topics', err); - } + this.emit(MqttClient.EVENTS.connected); - this.emit(MqttClient.EVENTS.connected); - }, - ); + // Testing to skip listening to HA birth messages all together + // this.client.subscribe( + // startTopics, + // { + // qos: 1, + // nl: true, // don't echo back messages sent + // rap: true, // retain as published - don't force retain = 0 + // rh: 0, // Retain handling 0 presumably ignores retained messages + // }, + // (err) => { + // if (err) { + // logger.error('Unable to subscribe to status topics', err); + // } - this.client.subscribe(getSubscribePath(), (err) => { - if (err) { - logger.error('Unable to subscribe to control topics'); - } - }); + // this.emit(MqttClient.EVENTS.connected); + // }, + // ); }); this.client.on('close', () => { @@ -197,64 +208,60 @@ class MqttClient extends EventEmitter { this.client.on('message', (topic, message) => { try { - if (startTopics.includes(topic)) { - logger.info('Home Assistant has started. lets do discovery.'); - this.emit(MqttClient.EVENTS.connected); - } else { - logger.verbose(`Received mqtt message on ${topic}`); - const decodedTopic = decodeTopic(topic); - if (decodedTopic) { - /** @type {import('types/DeviceRegistry').OutputDevice} */ - let device; + logger.verbose(`Received mqtt message on ${topic}`); + const decodedTopic = decodeTopic(topic); + if (decodedTopic) { + /** @type {import('types/DeviceRegistry').OutputDevice} */ + let device; - if (decodedTopic.type === MQTT_TYPES.SCENE) { - logger.verbose(`Getting scene ${decodedTopic.id} from registry`); - device = this.deviceRegistry.getScene(decodedTopic.id); - } else { - logger.verbose(`Getting device ${decodedTopic.id} from registry`); - device = this.deviceRegistry.getOutputDevice(decodedTopic.id); - } - - const messageString = message.toString(); - const isJsonMessage = messageString.startsWith('{'); - const command = isJsonMessage ? JSON.parse(messageString) : messageString; - - const deviceName = device ? device.name : ''; - - switch (decodedTopic.command) { - case 'set': - logger.verbose( - `Got mqtt SET command for ${decodedTopic.type}, ${deviceName} (${decodedTopic.id}): ${messageString}`, - ); - - if (device) { - this.emit(MqttClient.EVENTS.stateChanged, device, command); - } else { - logger.warn( - `Device for topic ${topic} not found! Can happen if HA calls previously existing devices.`, - ); - } - break; - case 'state': - case 'config': - case 'availability': - logger.verbose( - `Sent mqtt ${decodedTopic.command} command for ${ - decodedTopic.type - }, ${deviceName} (${decodedTopic.id}). ${ - decodedTopic.command === 'availability' ? messageString : '' - }`, - ); - break; - default: - logger.verbose(`Warning: Unknown command ${decodedTopic.command} in decoded topic`); - } + if (decodedTopic.type === MQTT_TYPES.SCENE) { + logger.verbose(`Getting scene ${decodedTopic.id} from registry`); + device = this.deviceRegistry.getScene(decodedTopic.id); } else { - logger.verbose( - `Warning: Got unrecognized mqtt command on '${topic}': ${message.toString()}`, - ); + logger.verbose(`Getting device ${decodedTopic.id} from registry`); + device = this.deviceRegistry.getOutputDevice(decodedTopic.id); } + + const messageString = message.toString(); + const isJsonMessage = messageString.startsWith('{'); + const command = isJsonMessage ? JSON.parse(messageString) : messageString; + + const deviceName = device ? device.name : ''; + + switch (decodedTopic.command) { + case 'set': + logger.verbose( + `Got mqtt SET command for ${decodedTopic.type}, ${deviceName} (${decodedTopic.id}): ${messageString}`, + ); + + if (device) { + this.emit(MqttClient.EVENTS.stateChanged, device, command); + } else { + logger.warn( + `Device for topic ${topic} not found! Can happen if HA calls previously existing devices.`, + ); + } + break; + case 'state': + case 'config': + case 'availability': + logger.verbose( + `Sent mqtt ${decodedTopic.command} command for ${ + decodedTopic.type + }, ${deviceName} (${decodedTopic.id}). ${ + decodedTopic.command === 'availability' ? messageString : '' + }`, + ); + break; + default: + logger.verbose(`Warning: Unknown command ${decodedTopic.command} in decoded topic`); + } + } else { + logger.verbose( + `Warning: Got unrecognized mqtt command on '${topic}': ${message.toString()}`, + ); } + // } } catch (err) { logger.error(`Error processing mqtt message on topic ${topic}`, err); } @@ -275,7 +282,7 @@ class MqttClient extends EventEmitter { const mqttType = outputDevice.type === 'switch' ? MQTT_TYPES.SWITCH : MQTT_TYPES.LIGHT; this.client.publish( getTopicName(outputDevice.uniqueId, mqttType, 'availability'), - AVAILABLILITY.OFFLINE, + AVAILABLITY.OFFLINE, { retain: true, qos: 1, @@ -287,7 +294,7 @@ class MqttClient extends EventEmitter { allSceneDevices.forEach((sceneDevice) => { this.client.publish( getTopicName(sceneDevice.uniqueId, MQTT_TYPES.SCENE, TOPIC_TYPES.AVAILABILITY), - AVAILABLILITY.OFFLINE, + AVAILABLITY.OFFLINE, { retain: true, qos: 1, @@ -298,37 +305,67 @@ class MqttClient extends EventEmitter { } sendDiscoveryToHomeAssistant() { + // -------- DISCOVERY FOR OUTPUT DEVICES ------------- + const allOutputDevices = this.deviceRegistry.getAllOutputDevices(); logger.info(`Sending discovery for ${allOutputDevices.length} Plejd output devices`); allOutputDevices.forEach((outputDevice) => { logger.debug(`Sending discovery for ${outputDevice.name}`); const configPayload = getOutputDeviceDiscoveryPayload(outputDevice); - logger.info( - `Discovered ${outputDevice.typeName} (${outputDevice.type}) named ${outputDevice.name} (${outputDevice.bleOutputAddress} : ${outputDevice.uniqueId}).`, - ); - + // Publish mqtt CONFIG message which will create the device in Home Assistant const mqttType = outputDevice.type === 'switch' ? MQTT_TYPES.SWITCH : MQTT_TYPES.LIGHT; this.client.publish( getTopicName(outputDevice.uniqueId, mqttType, TOPIC_TYPES.CONFIG), JSON.stringify(configPayload), { - retain: true, + retain: true, // Discovery messages should be retained to account for HA and MQTT broker restarts + qos: 1, + }, + ); + + logger.info( + `Sent discovery message for ${outputDevice.typeName} (${outputDevice.type}) named ${outputDevice.name} (${outputDevice.bleOutputAddress} : ${outputDevice.uniqueId}).`, + ); + + // -------- CLEANUP RETAINED MESSAGES FOR OUTPUT DEVICES ------------- + + logger.debug( + `Forcefully removing any retained SET, STATE, and AVAILABILITY messages for ${outputDevice.name}`, + ); + + // Forcefully remove retained (from Home Assistant) SET messages (wanted state from HA) + this.client.publish(getTopicName(outputDevice.uniqueId, mqttType, TOPIC_TYPES.SET), null, { + retain: true, // Retain true to remove previously retained message + qos: 1, + }); + + // Forcefully remove retained (from us, v0.11 and before) AVAILABILITY messages + this.client.publish( + getTopicName(outputDevice.uniqueId, mqttType, TOPIC_TYPES.AVAILABLILITY), + null, + { + retain: true, // Retain true to remove previously retained message + qos: 1, + }, + ); + + logger.debug(`Removal messages sent for ${outputDevice.name}`); + + logger.debug(`Setting device as AVAILABILITY = ONLINE: ${outputDevice.name}`); + + this.client.publish( + getTopicName(outputDevice.uniqueId, mqttType, TOPIC_TYPES.AVAILABILITY), + AVAILABLITY.ONLINE, + { + retain: false, // Availability messages should NOT be retained qos: 1, }, ); - setTimeout(() => { - this.client.publish( - getTopicName(outputDevice.uniqueId, mqttType, TOPIC_TYPES.AVAILABILITY), - AVAILABLILITY.ONLINE, - { - retain: true, - qos: 1, - }, - ); - }, 2000); }); + // -------- DISCOVERY FOR INPUT DEVICES ------------- + const allInputDevices = this.deviceRegistry.getAllInputDevices(); logger.info(`Sending discovery for ${allInputDevices.length} Plejd input devices`); allInputDevices.forEach((inputDevice) => { @@ -349,12 +386,14 @@ class MqttClient extends EventEmitter { getTopicName(inputDevice.uniqueId, MQTT_TYPES.DEVICE_AUTOMATION, TOPIC_TYPES.CONFIG), JSON.stringify(inputInputPayload), { - retain: true, + retain: true, // Discovery messages should be retained to account for HA restarts qos: 1, }, ); }); + // -------- DISCOVERY FOR SCENE DEVICES ------------- + const allSceneDevices = this.deviceRegistry.getAllSceneDevices(); logger.info(`Sending discovery for ${allSceneDevices.length} Plejd scene devices`); allSceneDevices.forEach((sceneDevice) => { @@ -369,7 +408,7 @@ class MqttClient extends EventEmitter { getTopicName(sceneDevice.uniqueId, MQTT_TYPES.SCENE, TOPIC_TYPES.CONFIG), JSON.stringify(sceneConfigPayload), { - retain: true, + retain: true, // Discovery messages should be retained to account for HA restarts qos: 1, }, ); @@ -384,22 +423,39 @@ class MqttClient extends EventEmitter { ), JSON.stringify(sceneTriggerConfigPayload), { - retain: true, + retain: true, // Discovery messages should be retained to account for HA restarts qos: 1, }, ); - setTimeout(() => { - this.client.publish( - getTopicName(sceneDevice.uniqueId, MQTT_TYPES.SCENE, TOPIC_TYPES.AVAILABILITY), - AVAILABLILITY.ONLINE, - { - retain: true, - qos: 1, - }, - ); - }, 2000); + // setTimeout(() => { + this.client.publish( + getTopicName(sceneDevice.uniqueId, MQTT_TYPES.SCENE, TOPIC_TYPES.AVAILABILITY), + AVAILABLITY.ONLINE, + { + retain: true, // Discovery messages should be retained to account for HA restarts + qos: 1, + }, + ); + // }, 2000); }); + + // -------- SUBSCRIBE TO INCOMING MESSAGES ------------- + + this.client.subscribe( + getSubscribePath(), + { + qos: 1, + nl: true, // don't echo back messages sent + rap: true, // retain as published - don't force retain = 0 + rh: 0, // Retain handling 0 presumably ignores retained messages + }, + (err) => { + if (err) { + logger.error('Unable to subscribe to control topics'); + } + }, + ); } /** @@ -440,13 +496,13 @@ class MqttClient extends EventEmitter { const mqttType = device.type === 'switch' ? MQTT_TYPES.SWITCH : MQTT_TYPES.LIGHT; this.client.publish(getTopicName(device.uniqueId, mqttType, TOPIC_TYPES.STATE), payload, { - retain: true, + retain: false, qos: 1, }); // this.client.publish( // getTopicName(device.uniqueId, mqttType, TOPIC_TYPES.AVAILABILITY), - // AVAILABLILITY.ONLINE, - // { retain: true, qos: 1 }, + // AVAILABILITY.ONLINE, + // { retain: false, qos: 1 }, // ); } @@ -456,7 +512,10 @@ class MqttClient extends EventEmitter { */ buttonPressed(deviceId, deviceInput) { logger.verbose(`Button ${deviceInput} pressed for deviceId ${deviceId}`); - this.client.publish(getButtonEventTopic(deviceId), `${deviceInput}`, { qos: 1 }); + this.client.publish(getButtonEventTopic(deviceId), `${deviceInput}`, { + retain: false, + qos: 1, + }); } /** @@ -464,7 +523,10 @@ class MqttClient extends EventEmitter { */ sceneTriggered(sceneId) { logger.verbose(`Scene triggered: ${sceneId}`); - this.client.publish(getSceneEventTopic(sceneId), '', { qos: 1 }); + this.client.publish(getSceneEventTopic(sceneId), '', { + qos: 1, + retain: false, + }); } } diff --git a/plejd/PlejdApi.js b/plejd/PlejdApi.js index 5642031..55edf93 100644 --- a/plejd/PlejdApi.js +++ b/plejd/PlejdApi.js @@ -11,9 +11,10 @@ const API_SITE_LIST_URL = 'functions/getSiteList'; const API_SITE_DETAILS_URL = 'functions/getSiteById'; const TRAITS = { - NO_LOAD: 0, - NON_DIMMABLE: 9, - DIMMABLE: 11, + NO_LOAD: 0, // 0b0000 + NON_DIMMABLE: 9, // 0b1001 + DIMMABLE: 11, // 0b1011 + DIMMABLE_COLORTEMP: 15, // 0b1111 }; const logger = Logger.getLogger('plejd-api'); @@ -338,8 +339,10 @@ class PlejdApi { description: 'Dali broadcast with dimmer and tuneable white support', type: 'light', dimmable: true, + colorTemp: true, broadcastClicks: false, }; + // 13: Non-dimmable generic light case 14: return { name: 'DIM-01', @@ -395,6 +398,7 @@ class PlejdApi { description: '1-channel LED dimmer/driver with tuneable white, 10 W', type: 'light', dimmable: true, + colorTemp: true, broadcastClicks: false, }; case 167: @@ -403,6 +407,7 @@ class PlejdApi { description: 'Smart tunable downlight with a built-in dimmer function, 8W', type: 'light', dimmable: true, + colorTemp: true, broadcastClicks: false, }; case 199: @@ -411,6 +416,7 @@ class PlejdApi { description: 'Smart tunable downlight with a built-in dimmer function, 8W', type: 'light', dimmable: true, + colorTemp: true, broadcastClicks: false, }; // PLEASE CREATE AN ISSUE WITH THE HARDWARE ID if you own one of these devices! @@ -480,8 +486,18 @@ class PlejdApi { (x) => x.deviceId === device.deviceId, ); - const dimmable = device.traits === TRAITS.DIMMABLE; - // dimmable = settings.dimCurve !== 'NonDimmable'; + const dimmable = + device.traits === TRAITS.DIMMABLE || device.traits === TRAITS.DIMMABLE_COLORTEMP; + + // Alternate approach looks at outputSettings.dimCurve and outputSettings.predefinedLoad + // 1. outputSettings.dimCurve === null: Not dimmable + // 2. outputSettings.dimCurve NOT IN ["NonDimmable", "RelayNormal"]: Dimmable + // 3. outputSettings.predefinedLoad !== null && outputSettings.predefinedLoad.loadType === "DWN": Dimmable + + const colorTemp = + outputSettings && + outputSettings.colorTemperature && + outputSettings.colorTemperature.behavior === 'adjustable'; try { const decodedDeviceType = this._getDeviceType(plejdDevice); @@ -499,6 +515,8 @@ class PlejdApi { /** @type {import('types/DeviceRegistry').OutputDevice} */ const outputDevice = { bleOutputAddress, + colorTemp, + colorTempSettings: outputSettings ? outputSettings.colorTemperature : null, deviceId: device.deviceId, dimmable, name: device.title, @@ -604,6 +622,7 @@ class PlejdApi { const newDevice = { bleOutputAddress: roomAddress, deviceId: null, + colorTemp: false, dimmable, name: room.title, output: undefined, @@ -633,6 +652,7 @@ class PlejdApi { /** @type {import('types/DeviceRegistry').OutputDevice} */ const newScene = { bleOutputAddress: sceneNum, + colorTemp: false, deviceId: undefined, dimmable: false, name: scene.title, diff --git a/plejd/PlejdBLEHandler.js b/plejd/PlejdBLEHandler.js index 7f50d55..aa68442 100644 --- a/plejd/PlejdBLEHandler.js +++ b/plejd/PlejdBLEHandler.js @@ -42,7 +42,10 @@ const GATT_CHRC_ID = 'org.bluez.GattCharacteristic1'; const PAYLOAD_POSITION_OFFSET = 5; const DIM_LEVEL_POSITION_OFFSET = 7; -const delay = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)); +const delay = (timeout) => + new Promise((resolve) => { + setTimeout(resolve, timeout); + }); class PlejBLEHandler extends EventEmitter { adapter; @@ -303,11 +306,14 @@ class PlejBLEHandler extends EventEmitter { // After we've authenticated, we need to hook up the event listener // for changes to lastData. - this.characteristics.lastDataProperties.on('PropertiesChanged', ( - iface, - properties, - // invalidated (third param), - ) => this._onLastDataUpdated(iface, properties)); + this.characteristics.lastDataProperties.on( + 'PropertiesChanged', + ( + iface, + properties, + // invalidated (third param), + ) => this._onLastDataUpdated(iface, properties), + ); this.characteristics.lastData.StartNotify(); this.consecutiveReconnectAttempts = 0; this.emit(PlejBLEHandler.EVENTS.connected); @@ -657,7 +663,7 @@ class PlejBLEHandler extends EventEmitter { logger.info('Requesting current Plejd time...'); const payload = this._createHexPayload( - this.connectedDevice.id, + this.connectedDeviceId, BLE_CMD_TIME_UPDATE, '', BLE_REQUEST_RESPONSE, diff --git a/plejd/README.md b/plejd/README.md index 5bfdc81..cb4ba8d 100644 --- a/plejd/README.md +++ b/plejd/README.md @@ -33,6 +33,7 @@ The add-on has been tested on the following platforms: - Windows 10 host / Oracle Virtualbox 6.1 / Home Assistant VBox image / ASUS BT400 - Mac OS Catalina 10.15.1 with Node v. 13.2.0 - Home Assistant Yellow with RPI 4 compute module / Built-in BT +- HP EliteDesk 800 G3 DM / Proxmox 7.5-3 / HAOS / Deltaco BT-118 with Cambridge Silicon Radio chipset Supported Plejd devices are detailed in a specific "Plejd devices" section below. diff --git a/plejd/package.json b/plejd/package.json index 0dc856a..2b8a4be 100644 --- a/plejd/package.json +++ b/plejd/package.json @@ -1,22 +1,22 @@ { "dependencies": { - "@abandonware/bluetooth-hci-socket": "~0.5.3-7", - "axios": "~0.21.1", + "@abandonware/bluetooth-hci-socket": "~0.5.3-10", + "axios": "~1.6.1", "buffer-xor": "~2.0.2", - "dbus-next": "~0.9.2", + "dbus-next": "~0.10.2", "fs": "0.0.1-security", "jspack": "~0.0.4", - "mqtt": "~4.2.6", - "winston": "~3.3.3" + "mqtt": "~5.1.2", + "winston": "~3.11.0" }, "devDependencies": { - "babel-eslint": "~10.1.0", - "eslint": "~7.23.0", - "eslint-config-airbnb": "~18.2.1", - "eslint-config-prettier": "~8.1.0", - "eslint-plugin-import": "~2.22.1", - "eslint-plugin-prettier": "~3.3.1", - "prettier": "~2.2.1" + "@babel/eslint-parser": "~7.23.3", + "eslint": "~8.53.0", + "eslint-config-airbnb": "~19.0.4", + "eslint-config-prettier": "~9.0.0", + "eslint-plugin-import": "~2.29.0", + "eslint-plugin-prettier": "~5.0.1", + "prettier": "~3.0.3" }, "scripts": { "lint": "npm run lint:prettier & npm run lint:scripts", diff --git a/plejd/rootfs/etc/services.d/plejd/run b/plejd/rootfs/etc/services.d/plejd/run index 14ef936..409652c 100644 --- a/plejd/rootfs/etc/services.d/plejd/run +++ b/plejd/rootfs/etc/services.d/plejd/run @@ -5,6 +5,7 @@ # ============================================================================== bashio::log.info 'Starting the Plejd service...' +bashio::log.info 'Docker env updated 2023-10-17...' # Change working directory cd /plejd || bashio::exit.nok 'Unable to change working directory' diff --git a/plejd/types/ApiSite.d.ts b/plejd/types/ApiSite.d.ts index db0be07..c8f41bf 100644 --- a/plejd/types/ApiSite.d.ts +++ b/plejd/types/ApiSite.d.ts @@ -260,6 +260,7 @@ export interface OutputSetting { deviceParseId: string; siteId: string; predefinedLoad: OutputSettingPredefinedLoad; + colorTemperature: OutputSettingColorTemperature; createdAt: Date; updatedAt: Date; dimMin: number; @@ -322,6 +323,16 @@ export interface OutputSettingPredefinedLoad { filters?: Filters; } +export interface OutputSettingColorTemperature { + "minTemperature": number, + "maxTemperature": number, + "slewRate": number, + "minTemperatureLimit": number, + "maxTemperatureLimit": number, + "behavior": "adjustable" | "UNKNOWN", // Todo: Fill with alternate values after finding more site jsons. UNKNOWN is placeholder for now. + "startTemperature": number +} + export interface PredefinedLoadACL { '*': Empty; } diff --git a/plejd/types/DeviceRegistry.d.ts b/plejd/types/DeviceRegistry.d.ts index 5eba5c8..b81ccb6 100644 --- a/plejd/types/DeviceRegistry.d.ts +++ b/plejd/types/DeviceRegistry.d.ts @@ -1,9 +1,13 @@ /* eslint-disable no-use-before-define */ +import { OutputSettingColorTemperature } from "./ApiSite"; + export type OutputDevices = { [deviceIdAndOutput: string]: OutputDevice }; export interface OutputDevice { bleOutputAddress: number; + colorTemp: boolean; + colorTempSettings?: OutputSettingColorTemperature deviceId: string; dim?: number; dimmable: boolean;