Skip to content

Commit

Permalink
Merge pull request #290 from icanos/develop
Browse files Browse the repository at this point in the history
Release 0.11.0
  • Loading branch information
SweVictor committed Oct 6, 2023
2 parents 17e382f + 1c0cb66 commit 37148c1
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 31 deletions.
21 changes: 20 additions & 1 deletion plejd/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
28 changes: 28 additions & 0 deletions plejd/DeviceRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class DeviceRegistry {

/** @private @type {Object.<string, import('types/ApiSite').Device>} */
devices = {};
/** @private @type {Object.<string, number>} */
mainBleIdByDeviceId = {};
/** @private @type {Object.<string, string[]>} */
outputDeviceUniqueIdsByRoomId = {};
/** @private @type {Object.<number, string>} */
Expand Down Expand Up @@ -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,
Expand All @@ -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] = [];
Expand Down Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion plejd/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down
48 changes: 36 additions & 12 deletions plejd/PlejdApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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/ --- `,
);
}
}

Expand Down
59 changes: 44 additions & 15 deletions plejd/PlejdBLEHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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')} */
Expand Down Expand Up @@ -140,6 +146,7 @@ class PlejBLEHandler extends EventEmitter {

this.bleDevices = [];
this.connectedDevice = null;
this.connectedDeviceId = null;

this.characteristics = {
data: null,
Expand Down Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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')}`);
Expand All @@ -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)}`);
}

Expand Down Expand Up @@ -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 ||
Expand All @@ -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);
Expand Down Expand Up @@ -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,
);
}
Expand Down
3 changes: 2 additions & 1 deletion plejd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | - | - | |
Expand Down
2 changes: 1 addition & 1 deletion plejd/config.json
Original file line number Diff line number Diff line change
@@ -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/",
Expand Down

0 comments on commit 37148c1

Please sign in to comment.