Merge pull request #165 from icanos/feature/time
Set Plejd devices' clock hourly
This commit is contained in:
commit
4ba534e229
3 changed files with 103 additions and 30 deletions
|
|
@ -16,10 +16,13 @@ const LAST_DATA_UUID = `31ba0005-${BLE_UUID_SUFFIX}`;
|
||||||
const AUTH_UUID = `31ba0009-${BLE_UUID_SUFFIX}`;
|
const AUTH_UUID = `31ba0009-${BLE_UUID_SUFFIX}`;
|
||||||
const PING_UUID = `31ba000a-${BLE_UUID_SUFFIX}`;
|
const PING_UUID = `31ba000a-${BLE_UUID_SUFFIX}`;
|
||||||
|
|
||||||
const BLE_CMD_DIM_CHANGE = 0xc8;
|
const BLE_CMD_DIM_CHANGE = 0x00c8;
|
||||||
const BLE_CMD_DIM2_CHANGE = 0x98;
|
const BLE_CMD_DIM2_CHANGE = 0x0098;
|
||||||
const BLE_CMD_STATE_CHANGE = 0x97;
|
const BLE_CMD_STATE_CHANGE = 0x0097;
|
||||||
const BLE_CMD_SCENE_TRIG = 0x21;
|
const BLE_CMD_SCENE_TRIG = 0x0021;
|
||||||
|
const BLE_CMD_TIME_UPDATE = 0x001b;
|
||||||
|
|
||||||
|
const BLE_BROADCAST_DEVICE_ID = 0x01;
|
||||||
|
|
||||||
const BLUEZ_SERVICE_NAME = 'org.bluez';
|
const BLUEZ_SERVICE_NAME = 'org.bluez';
|
||||||
const DBUS_OM_INTERFACE = 'org.freedesktop.DBus.ObjectManager';
|
const DBUS_OM_INTERFACE = 'org.freedesktop.DBus.ObjectManager';
|
||||||
|
|
@ -84,13 +87,13 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
logger.info('init()');
|
logger.info('init()');
|
||||||
|
|
||||||
this.bus = dbus.systemBus();
|
this.bus = dbus.systemBus();
|
||||||
this.bus.on('connect', () => {
|
|
||||||
logger.verbose('dbus-next connected');
|
|
||||||
});
|
|
||||||
this.bus.on('error', (err) => {
|
this.bus.on('error', (err) => {
|
||||||
// Uncaught error events will show UnhandledPromiseRejection logs
|
// Uncaught error events will show UnhandledPromiseRejection logs
|
||||||
logger.verbose(`dbus-next error event: ${err.message}`);
|
logger.verbose(`dbus-next error event: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
this.bus.on('connect', () => {
|
||||||
|
logger.verbose('dbus-next connected');
|
||||||
|
});
|
||||||
// this.bus also has a 'message' event that gets emitted _very_ frequently
|
// this.bus also has a 'message' event that gets emitted _very_ frequently
|
||||||
|
|
||||||
this.adapter = null;
|
this.adapter = null;
|
||||||
|
|
@ -216,7 +219,12 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
logger.info(`BLE Connected to ${this.connectedDevice.name}`);
|
logger.info(`BLE Connected to ${this.connectedDevice.name}`);
|
||||||
this.emit('connected');
|
this.emit('connected');
|
||||||
|
|
||||||
// Connected and authenticated, start ping
|
// Connected and authenticated, request current time and start ping
|
||||||
|
if (this.config.updatePlejdClock) {
|
||||||
|
this._requestCurrentPlejdTime();
|
||||||
|
} else {
|
||||||
|
logger.info('Plejd clock updates disabled in configuration.');
|
||||||
|
}
|
||||||
this.startPing();
|
this.startPing();
|
||||||
this.startWriteQueue();
|
this.startWriteQueue();
|
||||||
|
|
||||||
|
|
@ -663,6 +671,25 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
await this.onWriteSuccess();
|
await this.onWriteSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _requestCurrentPlejdTime() {
|
||||||
|
logger.info('Requesting current Plejd clock time...');
|
||||||
|
|
||||||
|
// Eg: 0b0102001b: 0b: id, 0102: read, 001b: time
|
||||||
|
const payload = Buffer.from(
|
||||||
|
`${this.connectedDevice.id.toString(16).padStart(2, '0')}0102${BLE_CMD_TIME_UPDATE.toString(
|
||||||
|
16,
|
||||||
|
).padStart(4, '0')}`,
|
||||||
|
'hex',
|
||||||
|
);
|
||||||
|
this.writeQueue.unshift({
|
||||||
|
deviceId: this.connectedDevice.id,
|
||||||
|
log: 'RequestTime',
|
||||||
|
shouldRetry: true,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
setTimeout(() => this._requestCurrentPlejdTime(), 1000 * 3600); // Once per hour
|
||||||
|
}
|
||||||
|
|
||||||
startWriteQueue() {
|
startWriteQueue() {
|
||||||
logger.info('startWriteQueue()');
|
logger.info('startWriteQueue()');
|
||||||
clearTimeout(this.writeQueueRef);
|
clearTimeout(this.writeQueueRef);
|
||||||
|
|
@ -852,15 +879,21 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
const deviceId = decoded.readUInt8(0);
|
const deviceId = decoded.readUInt8(0);
|
||||||
// What is bytes 2-3?
|
// Bytes 2-3 is Command/Request
|
||||||
const cmd = decoded.readUInt8(4);
|
const cmd = decoded.readUInt16BE(3);
|
||||||
|
|
||||||
const state = decoded.length > 5 ? decoded.readUInt8(5) : 0;
|
const state = decoded.length > 5 ? decoded.readUInt8(5) : 0;
|
||||||
// What is byte 6?
|
|
||||||
const dim = decoded.length > 7 ? decoded.readUInt8(7) : 0;
|
const dim = decoded.length > 7 ? decoded.readUInt8(7) : 0;
|
||||||
// Bytes 8-9 are sometimes present, what are they?
|
|
||||||
|
if (Logger.shouldLog('silly')) {
|
||||||
|
// Full dim level is 2 bytes, we could potentially use this
|
||||||
|
const dimFull = decoded.length > 7 ? decoded.readUInt16LE(6) : 0;
|
||||||
|
logger.silly(`Dim: ${dim.toString(16)}, full precision: ${dimFull.toString(16)}`);
|
||||||
|
}
|
||||||
|
|
||||||
const deviceName = this.deviceRegistry.getDeviceName(deviceId);
|
const deviceName = this.deviceRegistry.getDeviceName(deviceId);
|
||||||
if (Logger.shouldLog('debug')) {
|
if (Logger.shouldLog('verbose')) {
|
||||||
// decoded.toString() could potentially be expensive
|
// decoded.toString() could potentially be expensive
|
||||||
logger.verbose(`Raw event received: ${decoded.toString('hex')}`);
|
logger.verbose(`Raw event received: ${decoded.toString('hex')}`);
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
|
|
@ -900,10 +933,47 @@ class PlejBLEHandler extends EventEmitter {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.emit('sceneTriggered', deviceId, sceneId);
|
this.emit('sceneTriggered', deviceId, sceneId);
|
||||||
} else if (cmd === 0x1b) {
|
} else if (cmd === BLE_CMD_TIME_UPDATE) {
|
||||||
logger.silly(
|
const now = new Date();
|
||||||
'Command 001b is the time of the Plejd devices command, not implemented currently',
|
// Guess Plejd timezone based on HA time zone
|
||||||
);
|
const offsetSecondsGuess = now.getTimezoneOffset() * 60;
|
||||||
|
|
||||||
|
// Plejd reports local unix timestamp adjust to local time zone
|
||||||
|
const plejdTimestampUTC = (decoded.readInt32LE(5) + offsetSecondsGuess) * 1000;
|
||||||
|
const diffSeconds = Math.round((plejdTimestampUTC - now.getTime()) / 1000);
|
||||||
|
if (
|
||||||
|
deviceId !== BLE_BROADCAST_DEVICE_ID
|
||||||
|
|| Logger.shouldLog('verbose')
|
||||||
|
|| Math.abs(diffSeconds) > 60
|
||||||
|
) {
|
||||||
|
const plejdTime = new Date(plejdTimestampUTC);
|
||||||
|
logger.debug(
|
||||||
|
`Plejd clock time update ${plejdTime.toString()}, diff ${diffSeconds} seconds`,
|
||||||
|
);
|
||||||
|
if (this.config.updatePlejdClock && Math.abs(diffSeconds) > 60) {
|
||||||
|
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) {
|
||||||
|
const newLocalTimestamp = now.getTime() / 1000 - offsetSecondsGuess;
|
||||||
|
logger.info(`Setting time to ${now.toString()}`);
|
||||||
|
const payload = Buffer.alloc(10);
|
||||||
|
// E.g: 00 0110 001b 38df2360 00
|
||||||
|
// 00: set?, 0110: don't respond, 001b: time command, 38df236000: the time
|
||||||
|
payload.write('000110001b', 0, 'hex');
|
||||||
|
payload.writeInt32LE(Math.trunc(newLocalTimestamp), 5);
|
||||||
|
payload.write('00', 9, 'hex');
|
||||||
|
this.writeQueue.unshift({
|
||||||
|
deviceId: this.connectedDevice.id,
|
||||||
|
log: 'SetTime',
|
||||||
|
shouldRetry: true,
|
||||||
|
payload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (deviceId !== BLE_BROADCAST_DEVICE_ID) {
|
||||||
|
logger.info('Got time response. Plejd clock time in sync with Home Assistant time');
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
`Command ${cmd.toString(16)} unknown. ${decoded.toString(
|
`Command ${cmd.toString(16)} unknown. ${decoded.toString(
|
||||||
|
|
|
||||||
|
|
@ -121,20 +121,21 @@ The above is used to notify the add-on when Home Assistant has started successfu
|
||||||
|
|
||||||
The plugin needs you to configure some settings before working. You find these on the Add-on page after you've installed it.
|
The plugin needs you to configure some settings before working. You find these on the Add-on page after you've installed it.
|
||||||
|
|
||||||
| Parameter | Value |
|
| Parameter | Value |
|
||||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| site | Name of your Plejd site, the name is displayed in the Plejd app (top bar). |
|
| site | Name of your Plejd site, the name is displayed in the Plejd app (top bar). |
|
||||||
| username | Username of your Plejd account, this is used to fetch the crypto key and devices from the Plejd API. |
|
| username | Username of your Plejd account, this is used to fetch the crypto key and devices from the Plejd API. |
|
||||||
| password | Password of your Plejd account, this is used to fetch the crypto key and devices from the Plejd API. |
|
| password | Password of your Plejd account, this is used to fetch the crypto key and devices from the Plejd API. |
|
||||||
| mqttBroker | URL of the MQTT Broker, eg. mqtt://localhost |
|
| mqttBroker | URL of the MQTT Broker, eg. mqtt://localhost |
|
||||||
| mqttUsername | Username of the MQTT broker |
|
| mqttUsername | Username of the MQTT broker |
|
||||||
| mqttPassword | Password of the MQTT broker |
|
| mqttPassword | Password of the MQTT broker |
|
||||||
| includeRoomsAsLights | Adds all rooms as lights, making it possible to turn on/off lights by room instead. Setting this to false will ignore all rooms. |
|
| includeRoomsAsLights | Adds all rooms as lights, making it possible to turn on/off lights by room instead. Setting this to false will ignore all rooms. |
|
||||||
| logLevel | Minimim log level. Supported values are `error`, `warn`, `info`, `debug`, `verbose`, `silly` with increasing amount of logging. Do not log more than `info` for production purposes. |
|
| updatePlejdClock | Hourly update Plejd devices' clock if out of sync. Clock is used for time-based scenes. Not recommended if you have a Plejd gateway. Clock updates may flicker scene-controlled devices. |
|
||||||
| connectionTimeout | Number of seconds to wait when scanning and connecting. Might need to be tweaked on platforms other than RPi 4. Defaults to: 2 seconds. |
|
| logLevel | Minimim log level. Supported values are `error`, `warn`, `info`, `debug`, `verbose`, `silly` with increasing amount of logging. Do not log more than `info` for production purposes. |
|
||||||
| writeQueueWaitTime | Wait time between message sent to Plejd over BLE, defaults to 400. If that doesn't work, try changing the value higher in steps of 50. |
|
| connectionTimeout | Number of seconds to wait when scanning and connecting. Might need to be tweaked on platforms other than RPi 4. Defaults to: 2 seconds. |
|
||||||
|
| writeQueueWaitTime | Wait time between message sent to Plejd over BLE, defaults to 400. If that doesn't work, try changing the value higher in steps of 50. |
|
||||||
|
|
||||||
## Having issues to get the addon working?
|
## Troubleshooting
|
||||||
|
|
||||||
If you're having issues to get the addon working, there are a few things you can look into:
|
If you're having issues to get the addon working, there are a few things you can look into:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"mqttPassword": "",
|
"mqttPassword": "",
|
||||||
"includeRoomsAsLights": false,
|
"includeRoomsAsLights": false,
|
||||||
"preferCachedApiResponse": false,
|
"preferCachedApiResponse": false,
|
||||||
|
"updatePlejdClock": false,
|
||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
"connectionTimeout": 2,
|
"connectionTimeout": 2,
|
||||||
"writeQueueWaitTime": 400
|
"writeQueueWaitTime": 400
|
||||||
|
|
@ -32,6 +33,7 @@
|
||||||
"mqttPassword": "str",
|
"mqttPassword": "str",
|
||||||
"includeRoomsAsLights": "bool",
|
"includeRoomsAsLights": "bool",
|
||||||
"preferCachedApiResponse": "bool",
|
"preferCachedApiResponse": "bool",
|
||||||
|
"updatePlejdClock": "bool",
|
||||||
"logLevel": "list(error|warn|info|debug|verbose|silly)",
|
"logLevel": "list(error|warn|info|debug|verbose|silly)",
|
||||||
"connectionTimeout": "int",
|
"connectionTimeout": "int",
|
||||||
"writeQueueWaitTime": "int"
|
"writeQueueWaitTime": "int"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue