commit
37148c18c4
7 changed files with 132 additions and 31 deletions
|
|
@ -1,6 +1,25 @@
|
||||||
# Changelog hassio-plejd Home Assistant Plejd addon
|
# 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)
|
[Full Changelog](https://github.com/icanos/hassio-plejd/compare/0.9.1...0.10.0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ class DeviceRegistry {
|
||||||
|
|
||||||
/** @private @type {Object.<string, import('types/ApiSite').Device>} */
|
/** @private @type {Object.<string, import('types/ApiSite').Device>} */
|
||||||
devices = {};
|
devices = {};
|
||||||
|
/** @private @type {Object.<string, number>} */
|
||||||
|
mainBleIdByDeviceId = {};
|
||||||
/** @private @type {Object.<string, string[]>} */
|
/** @private @type {Object.<string, string[]>} */
|
||||||
outputDeviceUniqueIdsByRoomId = {};
|
outputDeviceUniqueIdsByRoomId = {};
|
||||||
/** @private @type {Object.<number, string>} */
|
/** @private @type {Object.<number, string>} */
|
||||||
|
|
@ -45,10 +47,26 @@ class DeviceRegistry {
|
||||||
this.outputUniqueIdByBleOutputAddress[
|
this.outputUniqueIdByBleOutputAddress[
|
||||||
this.getUniqueBLEId(inputDevice.bleInputAddress, inputDevice.input)
|
this.getUniqueBLEId(inputDevice.bleInputAddress, inputDevice.input)
|
||||||
] = inputDevice.uniqueId;
|
] = inputDevice.uniqueId;
|
||||||
|
|
||||||
|
if (!this.mainBleIdByDeviceId[inputDevice.deviceId]) {
|
||||||
|
this.mainBleIdByDeviceId[inputDevice.deviceId] = inputDevice.bleInputAddress;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param outputDevice {import('types/DeviceRegistry').OutputDevice} */
|
/** @param outputDevice {import('types/DeviceRegistry').OutputDevice} */
|
||||||
addOutputDevice(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 = {
|
||||||
...this.outputDevices,
|
...this.outputDevices,
|
||||||
[outputDevice.uniqueId]: outputDevice,
|
[outputDevice.uniqueId]: outputDevice,
|
||||||
|
|
@ -61,6 +79,9 @@ class DeviceRegistry {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.outputUniqueIdByBleOutputAddress[outputDevice.bleOutputAddress] = outputDevice.uniqueId;
|
this.outputUniqueIdByBleOutputAddress[outputDevice.bleOutputAddress] = outputDevice.uniqueId;
|
||||||
|
if (!this.mainBleIdByDeviceId[outputDevice.deviceId]) {
|
||||||
|
this.mainBleIdByDeviceId[outputDevice.deviceId] = outputDevice.bleOutputAddress;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId]) {
|
if (!this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId]) {
|
||||||
this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId] = [];
|
this.outputDeviceUniqueIdsByRoomId[outputDevice.roomId] = [];
|
||||||
|
|
@ -171,6 +192,13 @@ class DeviceRegistry {
|
||||||
return (this.inputDevices[uniqueInputId] || {}).name;
|
return (this.inputDevices[uniqueInputId] || {}).name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} deviceId
|
||||||
|
*/
|
||||||
|
getMainBleIdByDeviceId(deviceId) {
|
||||||
|
return this.mainBleIdByDeviceId[deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string } deviceId The physical device serial number
|
* @param {string } deviceId The physical device serial number
|
||||||
* @return {import('./types/ApiSite').Device}
|
* @return {import('./types/ApiSite').Device}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ const decodeTopic = (topic) => {
|
||||||
const getOutputDeviceDiscoveryPayload = (
|
const getOutputDeviceDiscoveryPayload = (
|
||||||
/** @type {import('./types/DeviceRegistry').OutputDevice} */ device,
|
/** @type {import('./types/DeviceRegistry').OutputDevice} */ device,
|
||||||
) => ({
|
) => ({
|
||||||
name: device.name,
|
name: null,
|
||||||
unique_id: device.uniqueId,
|
unique_id: device.uniqueId,
|
||||||
'~': getBaseTopic(device.uniqueId, device.type),
|
'~': getBaseTopic(device.uniqueId, device.type),
|
||||||
state_topic: `~/${TOPIC_TYPES.STATE}`,
|
state_topic: `~/${TOPIC_TYPES.STATE}`,
|
||||||
|
|
|
||||||
|
|
@ -332,17 +332,14 @@ class PlejdApi {
|
||||||
dimmable: true,
|
dimmable: true,
|
||||||
broadcastClicks: false,
|
broadcastClicks: false,
|
||||||
};
|
};
|
||||||
// DAL-01 is presumably a very special device
|
case 12:
|
||||||
// Please open a new issue if you have ideas on how to handel
|
return {
|
||||||
// Below could be use as testing, but since one device can have up to 64 slaves it probably won't work
|
name: 'DAL-01',
|
||||||
// case 12:
|
description: 'Dali broadcast with dimmer and tuneable white support',
|
||||||
// return {
|
type: 'light',
|
||||||
// name: 'DAL-01',
|
dimmable: true,
|
||||||
// description: 'Dali broadcast with dimmer and tuneable white support',
|
broadcastClicks: false,
|
||||||
// type: 'light',
|
};
|
||||||
// dimmable: true,
|
|
||||||
// broadcastClicks: false,
|
|
||||||
// };
|
|
||||||
case 14:
|
case 14:
|
||||||
return {
|
return {
|
||||||
name: 'DIM-01',
|
name: 'DIM-01',
|
||||||
|
|
@ -392,8 +389,35 @@ class PlejdApi {
|
||||||
dimmable: true,
|
dimmable: true,
|
||||||
broadcastClicks: false,
|
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:
|
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/ --- `,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ const BLUEZ_DEVICE_ID = 'org.bluez.Device1';
|
||||||
const GATT_SERVICE_ID = 'org.bluez.GattService1';
|
const GATT_SERVICE_ID = 'org.bluez.GattService1';
|
||||||
const GATT_CHRC_ID = 'org.bluez.GattCharacteristic1';
|
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 {
|
class PlejBLEHandler extends EventEmitter {
|
||||||
|
|
@ -47,7 +50,10 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
config;
|
config;
|
||||||
bleDevices = [];
|
bleDevices = [];
|
||||||
bus = null;
|
bus = null;
|
||||||
|
/** @type {import('types/ApiSite').Device} */
|
||||||
connectedDevice = null;
|
connectedDevice = null;
|
||||||
|
/** @type Number? */
|
||||||
|
connectedDeviceId = null;
|
||||||
consecutiveWriteFails;
|
consecutiveWriteFails;
|
||||||
consecutiveReconnectAttempts = 0;
|
consecutiveReconnectAttempts = 0;
|
||||||
/** @type {import('./DeviceRegistry')} */
|
/** @type {import('./DeviceRegistry')} */
|
||||||
|
|
@ -140,6 +146,7 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
|
|
||||||
this.bleDevices = [];
|
this.bleDevices = [];
|
||||||
this.connectedDevice = null;
|
this.connectedDevice = null;
|
||||||
|
this.connectedDeviceId = null;
|
||||||
|
|
||||||
this.characteristics = {
|
this.characteristics = {
|
||||||
data: null,
|
data: null,
|
||||||
|
|
@ -244,8 +251,8 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
await delay(this.config.connectionTimeout * 1000);
|
await delay(this.config.connectionTimeout * 1000);
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const connectedPlejdDevice = await this._onDeviceConnected(plejd);
|
const deviceWasConnected = await this._onDeviceConnected(plejd);
|
||||||
if (connectedPlejdDevice) {
|
if (deviceWasConnected) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +291,7 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
throw new Error('Could not connect to any Plejd device');
|
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
|
// Connected and authenticated, request current time and start ping
|
||||||
if (this.config.updatePlejdClock) {
|
if (this.config.updatePlejdClock) {
|
||||||
|
|
@ -770,9 +777,16 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('Connected device is a Plejd device with the right characteristics.');
|
|
||||||
|
|
||||||
this.connectedDevice = device.device;
|
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();
|
await this._authenticate();
|
||||||
|
|
||||||
return this.connectedDevice;
|
return this.connectedDevice;
|
||||||
|
|
@ -797,7 +811,7 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
const encryptedData = value.value;
|
const encryptedData = value.value;
|
||||||
const decoded = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, encryptedData);
|
const decoded = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, encryptedData);
|
||||||
|
|
||||||
if (decoded.length < 5) {
|
if (decoded.length < PAYLOAD_POSITION_OFFSET) {
|
||||||
if (Logger.shouldLog('debug')) {
|
if (Logger.shouldLog('debug')) {
|
||||||
// decoded.toString() could potentially be expensive
|
// decoded.toString() could potentially be expensive
|
||||||
logger.verbose(`Too short raw event ignored: ${decoded.toString('hex')}`);
|
logger.verbose(`Too short raw event ignored: ${decoded.toString('hex')}`);
|
||||||
|
|
@ -810,13 +824,18 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
// Bytes 2-3 is Command/Request
|
// Bytes 2-3 is Command/Request
|
||||||
const cmd = decoded.readUInt16BE(3);
|
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')) {
|
if (Logger.shouldLog('silly')) {
|
||||||
// Full dim level is 2 bytes, we could potentially use this
|
// 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)}`);
|
logger.silly(`Dim: ${dim.toString(16)}, full precision: ${dimFull.toString(16)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -867,12 +886,22 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
data = { sceneId: scene.uniqueId };
|
data = { sceneId: scene.uniqueId };
|
||||||
this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data);
|
this.emit(PlejBLEHandler.EVENTS.commandReceived, outputUniqueId, command, data);
|
||||||
} else if (cmd === BLE_CMD_TIME_UPDATE) {
|
} 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();
|
const now = new Date();
|
||||||
// Guess Plejd timezone based on HA time zone
|
// Guess Plejd timezone based on HA time zone
|
||||||
const offsetSecondsGuess = now.getTimezoneOffset() * 60 + 250; // Todo: 4 min off
|
const offsetSecondsGuess = now.getTimezoneOffset() * 60 + 250; // Todo: 4 min off
|
||||||
|
|
||||||
// Plejd reports local unix timestamp adjust to local time zone
|
// 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);
|
const diffSeconds = Math.round((plejdTimestampUTC - now.getTime()) / 1000);
|
||||||
if (
|
if (
|
||||||
bleOutputAddress !== BLE_BROADCAST_DEVICE_ID ||
|
bleOutputAddress !== BLE_BROADCAST_DEVICE_ID ||
|
||||||
|
|
@ -887,15 +916,15 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Plejd clock time off by more than 1 minute. Reported time: ${plejdTime.toString()}, diff ${diffSeconds} seconds. Time will be set hourly.`,
|
`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
|
// Requested time sync by us
|
||||||
const newLocalTimestamp = now.getTime() / 1000 - offsetSecondsGuess;
|
const newLocalTimestamp = now.getTime() / 1000 - offsetSecondsGuess;
|
||||||
logger.info(`Setting time to ${now.toString()}`);
|
logger.info(`Setting time to ${now.toString()}`);
|
||||||
const payload = this._createPayload(
|
const payload = this._createPayload(
|
||||||
this.connectedDevice.id,
|
this.connectedDeviceId,
|
||||||
BLE_CMD_TIME_UPDATE,
|
BLE_CMD_TIME_UPDATE,
|
||||||
10,
|
10,
|
||||||
(pl) => pl.writeInt32LE(Math.trunc(newLocalTimestamp), 5),
|
(pl) => pl.writeInt32LE(Math.trunc(newLocalTimestamp), PAYLOAD_POSITION_OFFSET),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
this._write(payload);
|
this._write(payload);
|
||||||
|
|
@ -947,8 +976,8 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
return this._createPayload(
|
return this._createPayload(
|
||||||
bleOutputAddress,
|
bleOutputAddress,
|
||||||
command,
|
command,
|
||||||
5 + Math.ceil(hexDataString.length / 2),
|
PAYLOAD_POSITION_OFFSET + Math.ceil(hexDataString.length / 2),
|
||||||
(payload) => payload.write(hexDataString, 5, 'hex'),
|
(payload) => payload.write(hexDataString, PAYLOAD_POSITION_OFFSET, 'hex'),
|
||||||
requestResponseCommand,
|
requestResponseCommand,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,8 @@ Plejd output devices typically appears as either lights or switches in Home Assi
|
||||||
| DIM-02 | - | Light | |
|
| DIM-02 | - | Light | |
|
||||||
| LED-10 | - | Light | |
|
| LED-10 | - | Light | |
|
||||||
| LED-75 | - | 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 |
|
| 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 |
|
| WRT-01 | - | Device Automation | type:button_short_press, subtype:button_1 |
|
||||||
| GWY-01 | - | - | |
|
| GWY-01 | - | - | |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Plejd",
|
"name": "Plejd",
|
||||||
"version": "0.10.0",
|
"version": "0.11.0",
|
||||||
"slug": "plejd",
|
"slug": "plejd",
|
||||||
"description": "Adds support for the Swedish home automation devices from Plejd.",
|
"description": "Adds support for the Swedish home automation devices from Plejd.",
|
||||||
"url": "https://github.com/icanos/hassio-plejd/",
|
"url": "https://github.com/icanos/hassio-plejd/",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue