diff --git a/plejd/CHANGELOG.md b/plejd/CHANGELOG.md index 2ccc750..90fe28a 100644 --- a/plejd/CHANGELOG.md +++ b/plejd/CHANGELOG.md @@ -1,6 +1,25 @@ # Changelog hassio-plejd Home Assistant Plejd addon -## [0.10.0](https://github.com/icanos/hassio-plejd/tree/0.10.0) +## [0.11.0](https://github.com/icanos/hassio-plejd/tree/0.11.0) (2023-10-06) + +[Full Changelog](https://github.com/icanos/hassio-plejd/compare/0.10.0...0.11.0) + +**Closed issues:** + +- DWN-01 [\#287](https://github.com/icanos/hassio-plejd/issues/287) +- Lights detected but cannot control them via HA. Going to unknown / unavailable in MQTT logbook [\#285](https://github.com/icanos/hassio-plejd/issues/285) +- 0.10.0 breaks DAL-01 [\#284](https://github.com/icanos/hassio-plejd/issues/284) +- Seems to just be looping [\#283](https://github.com/icanos/hassio-plejd/issues/283) +- Failed to start discovery. Operation already in progress [\#282](https://github.com/icanos/hassio-plejd/issues/282) +- Warning after HASS upgrade to 2023.8 [\#279](https://github.com/icanos/hassio-plejd/issues/279) +- Discovery timeout because of gateway [\#278](https://github.com/icanos/hassio-plejd/issues/278) +- Non-existing fields of connectedDevice are accessed in PlejdBLEHandler.js breaking time sync [\#265](https://github.com/icanos/hassio-plejd/issues/265) +- Unable to retrieve session token response: Request failed with status code 404 [\#264](https://github.com/icanos/hassio-plejd/issues/264) +- Used to work, can't find suitable bt device [\#263](https://github.com/icanos/hassio-plejd/issues/263) +- Reconnecting loop keeps going after devices found, this makes them unavailable. [\#261](https://github.com/icanos/hassio-plejd/issues/261) +- disconnecting after a few days [\#252](https://github.com/icanos/hassio-plejd/issues/252) + +## [0.10.0](https://github.com/icanos/hassio-plejd/tree/0.10.0) (2023-08-22) [Full Changelog](https://github.com/icanos/hassio-plejd/compare/0.9.1...0.10.0) diff --git a/plejd/DeviceRegistry.js b/plejd/DeviceRegistry.js index 951652e..491b92a 100644 --- a/plejd/DeviceRegistry.js +++ b/plejd/DeviceRegistry.js @@ -7,6 +7,8 @@ class DeviceRegistry { /** @private @type {Object.} */ devices = {}; + /** @private @type {Object.} */ + mainBleIdByDeviceId = {}; /** @private @type {Object.} */ outputDeviceUniqueIdsByRoomId = {}; /** @private @type {Object.} */ @@ -45,10 +47,26 @@ class DeviceRegistry { this.outputUniqueIdByBleOutputAddress[ this.getUniqueBLEId(inputDevice.bleInputAddress, inputDevice.input) ] = inputDevice.uniqueId; + + if (!this.mainBleIdByDeviceId[inputDevice.deviceId]) { + this.mainBleIdByDeviceId[inputDevice.deviceId] = inputDevice.bleInputAddress; + } } /** @param outputDevice {import('types/DeviceRegistry').OutputDevice} */ addOutputDevice(outputDevice) { + const alreadyExistingBLEDevice = this.getOutputDeviceByBleOutputAddress( + outputDevice.bleOutputAddress, + ); + if (alreadyExistingBLEDevice) { + logger.warn( + `Device with output id ${outputDevice.bleOutputAddress} already exists named ${alreadyExistingBLEDevice.name}. These two devices are probably grouped in the Plejd app. If this seems to be an error, please log a GitHub issue.`, + ); + logger.info(`NOT adding ${outputDevice.name} to device registry`); + logger.verbose(`Details of device NOT added: ${JSON.stringify(outputDevice)}`); + return; + } + this.outputDevices = { ...this.outputDevices, [outputDevice.uniqueId]: outputDevice, @@ -61,6 +79,9 @@ class DeviceRegistry { ); this.outputUniqueIdByBleOutputAddress[outputDevice.bleOutputAddress] = outputDevice.uniqueId; + if (!this.mainBleIdByDeviceId[outputDevice.deviceId]) { + this.mainBleIdByDeviceId[outputDevice.deviceId] = outputDevice.bleOutputAddress; + } if (!this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId]) { this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId] = []; @@ -171,6 +192,13 @@ class DeviceRegistry { return (this.inputDevices[uniqueInputId] || {}).name; } + /** + * @param {string} deviceId + */ + getMainBleIdByDeviceId(deviceId) { + return this.mainBleIdByDeviceId[deviceId]; + } + /** * @param {string } deviceId The physical device serial number * @return {import('./types/ApiSite').Device} diff --git a/plejd/MqttClient.js b/plejd/MqttClient.js index 57f1262..3517041 100644 --- a/plejd/MqttClient.js +++ b/plejd/MqttClient.js @@ -58,7 +58,7 @@ const decodeTopic = (topic) => { const getOutputDeviceDiscoveryPayload = ( /** @type {import('./types/DeviceRegistry').OutputDevice} */ device, ) => ({ - name: device.name, + name: null, unique_id: device.uniqueId, '~': getBaseTopic(device.uniqueId, device.type), state_topic: `~/${TOPIC_TYPES.STATE}`, diff --git a/plejd/PlejdApi.js b/plejd/PlejdApi.js index cc12990..fffcc2f 100644 --- a/plejd/PlejdApi.js +++ b/plejd/PlejdApi.js @@ -332,17 +332,14 @@ class PlejdApi { dimmable: true, broadcastClicks: false, }; - // DAL-01 is presumably a very special device - // Please open a new issue if you have ideas on how to handel - // Below could be use as testing, but since one device can have up to 64 slaves it probably won't work - // case 12: - // return { - // name: 'DAL-01', - // description: 'Dali broadcast with dimmer and tuneable white support', - // type: 'light', - // dimmable: true, - // broadcastClicks: false, - // }; + case 12: + return { + name: 'DAL-01', + description: 'Dali broadcast with dimmer and tuneable white support', + type: 'light', + dimmable: true, + broadcastClicks: false, + }; case 14: return { name: 'DIM-01', @@ -392,8 +389,35 @@ class PlejdApi { dimmable: true, broadcastClicks: false, }; + case 167: + return { + name: 'DWN-01', + description: 'Smart tunable downlight with a built-in dimmer function, 8W', + type: 'light', + dimmable: true, + broadcastClicks: false, + }; + // PLEASE CREATE AN ISSUE WITH THE HARDWARE ID if you own one of these devices! + // case + // return { + // name: 'DWN-02', + // description: 'Smart tunable downlight with a built-in dimmer function, 8W', + // type: 'light', + // dimmable: true, + // broadcastClicks: false, + // }; + // case + // return { + // name: 'OUT-01', + // description: 'Outdoor wall light with built-in LED, 2x5W', + // type: 'light', + // dimmable: true, + // broadcastClicks: false, + // }; default: - throw new Error(`Unknown device type with id ${plejdDevice.hardwareId}`); + throw new Error( + `Unknown device type with hardware id ${plejdDevice.hardwareId}. --- PLEASE POST THIS AND THE NEXT LOG ROWS to https://github.com/icanos/hassio-plejd/issues/ --- `, + ); } } diff --git a/plejd/PlejdBLEHandler.js b/plejd/PlejdBLEHandler.js index 6e943d8..7f50d55 100644 --- a/plejd/PlejdBLEHandler.js +++ b/plejd/PlejdBLEHandler.js @@ -39,6 +39,9 @@ const BLUEZ_DEVICE_ID = 'org.bluez.Device1'; const GATT_SERVICE_ID = 'org.bluez.GattService1'; 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)); class PlejBLEHandler extends EventEmitter { @@ -47,7 +50,10 @@ class PlejBLEHandler extends EventEmitter { config; bleDevices = []; bus = null; + /** @type {import('types/ApiSite').Device} */ connectedDevice = null; + /** @type Number? */ + connectedDeviceId = null; consecutiveWriteFails; consecutiveReconnectAttempts = 0; /** @type {import('./DeviceRegistry')} */ @@ -140,6 +146,7 @@ class PlejBLEHandler extends EventEmitter { this.bleDevices = []; this.connectedDevice = null; + this.connectedDeviceId = null; this.characteristics = { data: null, @@ -244,8 +251,8 @@ class PlejBLEHandler extends EventEmitter { await delay(this.config.connectionTimeout * 1000); // eslint-disable-next-line no-await-in-loop - const connectedPlejdDevice = await this._onDeviceConnected(plejd); - if (connectedPlejdDevice) { + const deviceWasConnected = await this._onDeviceConnected(plejd); + if (deviceWasConnected) { break; } } @@ -284,7 +291,7 @@ class PlejBLEHandler extends EventEmitter { throw new Error('Could not connect to any Plejd device'); } - logger.info(`BLE Connected to ${this.connectedDevice.name}`); + logger.info(`BLE Connected to ${this.connectedDevice.title}`); // Connected and authenticated, request current time and start ping if (this.config.updatePlejdClock) { @@ -770,9 +777,16 @@ class PlejBLEHandler extends EventEmitter { return null; } - logger.info('Connected device is a Plejd device with the right characteristics.'); - this.connectedDevice = device.device; + this.connectedDeviceId = this.deviceRegistry.getMainBleIdByDeviceId( + this.connectedDevice.deviceId, + ); + + logger.verbose('The connected Plejd device has the right charecteristics!'); + logger.info( + `Connected to Plejd device ${this.connectedDevice.title} (${this.connectedDevice.deviceId}, BLE id ${this.connectedDeviceId}).`, + ); + await this._authenticate(); return this.connectedDevice; @@ -797,7 +811,7 @@ class PlejBLEHandler extends EventEmitter { const encryptedData = value.value; const decoded = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, encryptedData); - if (decoded.length < 5) { + if (decoded.length < PAYLOAD_POSITION_OFFSET) { if (Logger.shouldLog('debug')) { // decoded.toString() could potentially be expensive logger.verbose(`Too short raw event ignored: ${decoded.toString('hex')}`); @@ -810,13 +824,18 @@ class PlejBLEHandler extends EventEmitter { // Bytes 2-3 is Command/Request const cmd = decoded.readUInt16BE(3); - const state = decoded.length > 5 ? decoded.readUInt8(5) : 0; + const state = + decoded.length > PAYLOAD_POSITION_OFFSET ? decoded.readUInt8(PAYLOAD_POSITION_OFFSET) : 0; - const dim = decoded.length > 7 ? decoded.readUInt8(7) : 0; + const dim = + decoded.length > DIM_LEVEL_POSITION_OFFSET ? decoded.readUInt8(DIM_LEVEL_POSITION_OFFSET) : 0; if (Logger.shouldLog('silly')) { // Full dim level is 2 bytes, we could potentially use this - const dimFull = decoded.length > 7 ? decoded.readUInt16LE(6) : 0; + const dimFull = + decoded.length > DIM_LEVEL_POSITION_OFFSET + ? decoded.readUInt16LE(DIM_LEVEL_POSITION_OFFSET - 1) + : 0; logger.silly(`Dim: ${dim.toString(16)}, full precision: ${dimFull.toString(16)}`); } @@ -867,12 +886,22 @@ class PlejBLEHandler extends EventEmitter { data = { sceneId: scene.uniqueId }; this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data); } else if (cmd === BLE_CMD_TIME_UPDATE) { + if (decoded.length < PAYLOAD_POSITION_OFFSET + 4) { + if (Logger.shouldLog('debug')) { + // decoded.toString() could potentially be expensive + logger.verbose(`Too short time update event ignored: ${decoded.toString('hex')}`); + } + // ignore the notification since too small + return; + } + const now = new Date(); // Guess Plejd timezone based on HA time zone const offsetSecondsGuess = now.getTimezoneOffset() * 60 + 250; // Todo: 4 min off // Plejd reports local unix timestamp adjust to local time zone - const plejdTimestampUTC = (decoded.readInt32LE(5) + offsetSecondsGuess) * 1000; + const plejdTimestampUTC = + (decoded.readInt32LE(PAYLOAD_POSITION_OFFSET) + offsetSecondsGuess) * 1000; const diffSeconds = Math.round((plejdTimestampUTC - now.getTime()) / 1000); if ( bleOutputAddress !== BLE_BROADCAST_DEVICE_ID || @@ -887,15 +916,15 @@ 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 && bleOutputAddress === this.connectedDevice.id) { + if (this.connectedDevice && bleOutputAddress === this.connectedDeviceId) { // Requested time sync by us const newLocalTimestamp = now.getTime() / 1000 - offsetSecondsGuess; logger.info(`Setting time to ${now.toString()}`); const payload = this._createPayload( - this.connectedDevice.id, + this.connectedDeviceId, BLE_CMD_TIME_UPDATE, 10, - (pl) => pl.writeInt32LE(Math.trunc(newLocalTimestamp), 5), + (pl) => pl.writeInt32LE(Math.trunc(newLocalTimestamp), PAYLOAD_POSITION_OFFSET), ); try { this._write(payload); @@ -947,8 +976,8 @@ class PlejBLEHandler extends EventEmitter { return this._createPayload( bleOutputAddress, command, - 5 + Math.ceil(hexDataString.length / 2), - (payload) => payload.write(hexDataString, 5, 'hex'), + PAYLOAD_POSITION_OFFSET + Math.ceil(hexDataString.length / 2), + (payload) => payload.write(hexDataString, PAYLOAD_POSITION_OFFSET, 'hex'), requestResponseCommand, ); } diff --git a/plejd/README.md b/plejd/README.md index 7b61943..7e0c8da 100644 --- a/plejd/README.md +++ b/plejd/README.md @@ -144,7 +144,8 @@ Plejd output devices typically appears as either lights or switches in Home Assi | DIM-02 | - | Light | | | LED-10 | - | Light | | | LED-75 | - | Light | | -| DAL-01 | - | - | Not tested, not supported | +| DAL-01 | - | Light | | +| DWN-01 | - | Light | | | WPH-01 | - | Device Automation | type:button_short_press, subtype:button_1, button_2,button_3,button_4 | | WRT-01 | - | Device Automation | type:button_short_press, subtype:button_1 | | GWY-01 | - | - | | diff --git a/plejd/config.json b/plejd/config.json index 36d6896..8a05d03 100644 --- a/plejd/config.json +++ b/plejd/config.json @@ -1,6 +1,6 @@ { "name": "Plejd", - "version": "0.10.0", + "version": "0.11.0", "slug": "plejd", "description": "Adds support for the Swedish home automation devices from Plejd.", "url": "https://github.com/icanos/hassio-plejd/",