Open SweVictor opened 3 years ago
Some random thoughts since you pinged me..
Some data in the plejd API is little endian, for example the dim level is 16 bits little endian. The commands are two bytes (big endian..)
command types: 0110 = command 0102 = read
I can probably have other thoughts, but this is as structured as they are right now.
One other interesting revelation I had was that it's possible to query current state on the light_level uuid, I implemented that at https://github.com/klali/ha-plejd/commit/de1333460dcb67750a22357dc60064db012a61eb @emilohman realised that this can be triggered as reads on one output at a time as well to get only specific device states.
Thanks for that @klali! I actually looked just the other day at that specific commit you referenced, and realized that is something not implemented in this code-base. Haven't had time to look into what it actually does though. I tried my best based on just looking at your code to write down what it writes/decodes, might not be accurate.
The dim level is interesting that you mention. Is what you're saying Plejd actually have 2 byte dim level? So not 0-255 but rather 0-65535? We recently switched to parsing byte 8 (index 7) rather than byte 7 (index 6) but maybe we're then just decoding the most significant byte and discarding the rest?
Yes, dim is two bytes, little-endian. If you let the bytes swap places and decode it it will make sense. This isn't very useful from home-assistant since dim is only one byte there, but to reach highest dim levels you need to set it to ffff.
Yes, I didn't completely decode the lightlevel data, but it's reported one or two outputs at a time, with 10 bytes per output: 0 -> id 1 -> state 2-4 -> ? 5-6 -> dim (little-endian..) 7-9 -> ?
I see. I realize now that we actually do set the full two bytes when setting dim level (const brightnessVal = (brightness << 8) | brightness;
), we however only parse the most significant byte and represent the value internally as only one byte. As you write it might not be the most important thing for HA, but we could potentially use it for transitioning or something else (and it's of course good to know as much as possible about incoming messages).
And as I look at what you wrote about time, remember that it's unix time, so 32 bits (little-endian) since 1970-01-01 00:00, so: 2aeb2360 -> 1612966698 -> Wed Feb 10 15:18:18 2021
Several of the commands seem to have a trailing byte, I have no idea what that means.
Thanks, clarified in the text!
Have been doiing some testing. Interestingly enough it seems you can broadcast light commands to device id 00
to set all lights at once (without any delay). Which brings the question: anyone knows the difference between devices 00
, 01
and 02
, which all seem to be "special"? Time is using 01
, scenes in my installation seem to be using 02
.
Btw - thanks to the discussion here this repo now has a PR for time reading/updates as well as a better handle on dim levels and little-endian encoding, so thanks for that! (btw 2: Makes me think the command are actually 0x9700
little-endian rather than 0x0097
big-endian, not that it matters 😉 )
General format of incoming messages
device id | command/read | command | data |
---|---|---|---|
00 | 0110 | 0016 | 00 dc0a // 01 d90a // 02 d90a // 03 d90a |
BLE device Id Device 00 is a "broadcast" message used by Bluetooth buttons WPH-01 and WRT-01
Command/request 0110: Command (no response)
Command
0016:
Button pressed
Data
For command 0016
(Button pressed), first byte of data represents which button that has been pressed.
Unit | Command | Data [0] | Button representation | Event in home assistant |
---|---|---|---|---|
WPH-01 | 0016 | 00 | Top left | button_short_press, `button_1` |
WPH-01 | 0016 | 01 | Top right | button_short_press, `button_2` |
WPH-01 | 0016 | 02 | Bottom left | button_short_press, `button_3` |
WPH-01 | 0016 | 03 | Bottom right | button_short_press, `button_4` |
WRT-01 | 0016 | 00 | Rotary button | button_short_press, `button_1` |
Thanks, added in first post. Left to find out: Rotation of RTR-01 rotary encoder and what BLE commands that sends.
Rotation of RTR-01 rotary encoder and what BLE commands that sends.
RTR-01 is just another input physically/electrically attached to a Plejd Device like DIM-01, etc. RTR-01 have no Bluetooth and are not sending any BLE commands. It is the host of the RTR-01 that sends the BLE commands, and there is nothing Broadcast for RTR-01 at all that I have seen. Just like any other input on any device, nothing is broadcast at click.
When using RTR-01 towards another device than the host it must be configured towards that other device, just like any other input.
WPH-01 and WTR-01 are different from RTR-01 by being battery powered Bluetooth devices without loads.
Sadly the WRT-01 does not broadcast on rotation. Just like RTR-01 it will only send targeted commands/rotation/dimming after having been configured to a target device.
Sadly the WRT-01 does not broadcast on rotation. Just like RTR-01 it will only send targeted commands/rotation/dimming after having been configured to a target device.
Oh, I didn't realize that was the the case, shame. So - if no device is set as output it sends nothing? And if a device is connected it sends dim command as per usual?
Oh, I didn't realize that was the the case, shame. So - if no device is set as output it sends nothing?
Correct. It only broadcast on click.
And if a device is connected it sends dim command as per usual?
Correct.
For more info, see post from @vBrolin. "Nothing on turn"
Command 0420
device id | request to respond? | command | unknown | color temp |
---|---|---|---|---|
ID | 0110 | 0420 | 030111 | CC CC |
unknown
bytes has always been 03 01 11 in what I have seencolor temp
is the color temperature in Kelvin. Two bytes, encoded big-endianrxAddress
in the cloud API site data, but it works if you send it to the normal outputAddress
tooI have tested this on one DWN-01, but it should work with DWN-02 and probably LED-75 too.
Color temperature ...
Much appreciated, thanks! Looking at my site json response I don't have any rxAddress
. My site.version
is 1447
- maybe that version is the reason the site json looks different for different installations? Also gateway/not seems to affect things.
@thomasloven could you possibly post a (scrubbed of course) version of yours in some way? There seems to be some difference with DWN-01 (and presumably at least DWN-02 as well) don't register themselves as dimmable in the same way as other devices (#295)
No, that's right.
For DWN-01 you have to look into outputSettings.predefinedLoad.loadType
which is DWN
, and is dimmable despite outputSettings.dimCurve
being NonDimmable
. https://github.com/thomasloven/pyplejd/blob/91e7abfd9d44cddfa14abf919afa01cfca84bce3/pyplejd/cloud/__init__.py#L152-L158
Quite annoying, and it honestly seems like Pljed are just making up things as they go. As for example in the color temperature being BIG endian while the dim value is LITTLE...
Here's the site details for my test setup: https://gist.github.com/thomasloven/b53ae38ea2971c319618a848e02c0234
For DWN-01 you have to look into
outputSettings.predefinedLoad.loadType
which isDWN
, and is dimmable despiteoutputSettings.dimCurve
beingNonDimmable
. https://github.com/thomasloven/pyplejd/blob/91e7abfd9d44cddfa14abf919afa01cfca84bce3/pyplejd/cloud/__init__.py#L152-L158
Perfect, thanks for that!
just making up things as they go It absolutely feels like that! 😆 And a lot of redundant-looking info in the json as well, so quite hard to know what values to trust.
DWN-X are not guaranteed to be tunable, by the way. They can be set up to follow the astrotable for the color temperature in which case I believe they will reject any manual settings. See the lines below what I linked above.
For DWN-01 you have to look into
outputSettings.predefinedLoad.loadType
which isDWN
, and is dimmable despiteoutputSettings.dimCurve
beingNonDimmable
.
@thomasloven Looking through your site JSON a bit more carefully and comparing it to our code I note that for the DWN-01 there is a new traits
value.
We (in this repo) use traits
to set capabilities (dimmable mostly). We used to use loadType etc, but that gave us some issues. I'm thinking that the "new" 15
value might be dim + color temperature. That would then give us
NO_LOAD: 0, // 0b0001
NON_DIMMABLE: 9, // 0b1001
DIMMABLE: 11, // 0b1011
DIMMABLE_COLORTEMP: 15, // 0b1111
I added the binary equivalents above, which seem to be reasonable. abcd
would then mean:
a
Same as d
, probably signifies something differentb
Supports color temperaturec
Supports dimmingd
Has some load definedI've added this as a test to fix that DWN are currently not dimmable in the https://github.com/icanos/hassio-plejd/tree/feature/DWN-dimmable-fix branch.
Thoughts?
We would really need some more examples to know for sure, but I'm feeling lucky today 😄
Seems to make sense. I have site JSON from a user with some DWN-2 also, and they have traits either 15 or 9.
I'm not sure how it works, but I guess those can be grouped in some way.
The ones that have traits: 9
also have a property in plejdDevices
called isFellowshipFollower
set to true
and no predefinedLoad
.
Unfortunately they only had one in stock at elbutik.se when I ordered mine for testing, so I can't test the grouping...
{
"deviceId": "C3E245730751",
"siteId": "e6f7cddb-6582-4c39-b485-6982803b5f0f",
"title": "Downlights",
"traits": 15,
"hiddenFromRoomList": false,
"roomId": "fb9f0653-3ceb-45f2-bca8-50321c704cc8",
"createdAt": "2023-09-09T23:20:52.033Z",
"updatedAt": "2023-09-10T19:55:12.605Z",
"hiddenFromIntegrations": false,
"outputType": "LIGHT",
"ACL": {},
"objectId": "NJxc5qbS8B",
"__type": "Object",
"className": "Device"
},
{
"deviceId": "D2B437D3D6C3",
"siteId": "e6f7cddb-6582-4c39-b485-6982803b5f0f",
"title": "Downlights",
"traits": 9,
"hiddenFromRoomList": false,
"roomId": "fb9f0653-3ceb-45f2-bca8-50321c704cc8",
"createdAt": "2023-09-09T23:20:53.286Z",
"updatedAt": "2023-09-09T23:20:53.286Z",
"ACL": {},
"objectId": "63ggVxw5aC",
"__type": "Object",
"className": "Device"
},
{
"deviceId": "C3E245730751",
"siteId": ...,
"installer": {
...
},
"dirtyInstall": false,
"dirtyUpdate": false,
"dirtyClock": false,
"dirtySettings": false,
"hardwareId": "199",
"faceplateId": "0",
"faceplateUpdatedAt": "2023-09-09T23:20:52.029Z",
"firmware": {
...
},
"createdAt": "2023-09-09T23:20:52.033Z",
"updatedAt": "2023-09-10T19:53:43.192Z",
"isFellowshipFollower": false,
"coordinates": {
"__type": "GeoPoint",
"latitude": 68.6974329,
"longitude": 15.1949525
},
"predefinedLoad": {
"loadType": "DWN",
"descriptionKey": "DWNDescription",
"titleKey": "DWNTitle",
"predefinedLoadData": "{\n \"Order\":1,\n \"Min\":0.5,\n \"Max\":100,\n \"Start\":0.5,\n \"OutputSpeed\":0.25,\n \"ColorTemperature\":{\n \"behavior\":\"dimToWarm\",\n \"logFactor\":105,\n \"slewRate\":6554,\n \"minDimLevel\":25,\n \"maxDimLevel\":255,\n \"minTemperatureLimit\":2200,\n \"maxTemperatureLimit\":4000,\n \"minTemperature\":2200,\n \"maxTemperature\":3200\n },\n \"MinDimLevelMapping\":{\n \"0%\":15,\n \"0.1%\":19,\n \"0.2%\":23,\n \"0.3%\":29,\n \"0.4%\":35,\n \"0.5%\":44,\n \"0.6%\":76,\n \"0.7%\":130,\n \"0.8%\":222,\n \"0.9%\":382\n },\n \"OutputType\":\"LIGHT\",\n \"BootState\":\"UseLast\",\n \"UserDefined\":[\n \"ColorTemperature\"\n ],\n \"Settings\":[\n \"SimpleStart\",\n \"Max\",\n \"ColorTemperature\"\n ]\n}",
"createdAt": "2023-05-16T14:37:27.700Z",
"updatedAt": "2023-06-22T13:01:16.366Z",
"defaultDimCurve": {
"__type": "Pointer",
"className": "DimCurve",
"objectId": "xGBw2qRHoE"
},
"allowedDimCurves": {
"__type": "Relation",
"className": "DimCurve"
},
"ACL": {},
"objectId": "G9rgAQ8X6B",
"__type": "Object",
"className": "PredefinedLoad"
},
"diagnostics": "0000170000003200000000000000",
"ACL": {},
"objectId": "wpjCzRm0xz",
"__type": "Object",
"className": "PlejdDevice"
},
{
"deviceId": "D2B437D3D6C3",
"siteId": ...,
"installer": {
...
},
"dirtyInstall": true,
"dirtyUpdate": false,
"dirtyClock": false,
"dirtySettings": false,
"hardwareId": "199",
"faceplateId": "0",
"faceplateUpdatedAt": "2023-09-09T23:20:53.225Z",
"isFellowshipFollower": true,
"firmware": {
...
},
"createdAt": "2023-09-09T23:20:53.286Z",
"updatedAt": "2023-09-10T19:59:37.014Z",
"diagnostics": "0000150000003400000000000000",
"ACL": {},
"objectId": "D3HEfE6djd",
"__type": "Object",
"className": "PlejdDevice"
},
I got a WMS-01 motion sensor. It will send events on LASTDATA when motion is detected, whether or not it is paired to a light.
device id | request to respond? | command | unknown 1 | unknown 2 | light level |
---|---|---|---|---|---|
ID | 0110 | 0420 | 03031f | 0700b10f084616 | 02f0 |
command
is the same as for color temperature commands, but unknown 1
is different. That was 030111
for my DWN-01 but always 03031f
here.
unknown 2
I have no idea about. Once I saw it was 0f00b0...
but every other time it's been 0f00b1...
.
light level
seems to be the light level of the room in big endian encoding. With my strongest light I can nearly push it up to ffff
, but I don't know the range or the unit yet.
Edit: I just realized that part of
unknown 2
may be likely to be the battery voltage. This thing has a standard AA battery.
There are no events sent when no more motion is detected. The cooldown between detection events seems to be just over 30 seconds.
The sensitivity can be set in the app. I've been playing around with it a little bit, but can't see much difference in either behavior or in the siteData.
siteData.devices
:
{
"deviceId": "EE2FE8EBFE52",
"siteId": "<REDACTED>",
"title": "H\u00f6rn",
"traits": 0,
"hiddenFromRoomList": false,
"roomId": "40e2a007-9445-4e5b-821a-4e922ba8fd47",
"createdAt": "2024-02-20T19:52:25.976Z",
"updatedAt": "2024-02-20T19:52:25.976Z",
"ACL": {},
"objectId": "poSqxqgd8X",
"__type": "Object",
"className": "Device"
}
siteData.plejdDevices
:
{
"deviceId": "EE2FE8EBFE52",
"siteId": "<REDACTED>",
"installer": "<REDACTED>",
"dirtyInstall": false,
"dirtyUpdate": false,
"dirtyClock": false,
"dirtySettings": false,
"hardwareId": "70",
"faceplateId": "0",
"faceplateUpdatedAt": "2024-02-20T19:52:25.967Z",
"firmware": {
"notes": "WMS-01",
"data": {
"__type": "File",
"name": "e6bde80da922879ee5cf7d6d47960593_application.bin",
"url": "https://cloud.plejd.com/parse/files/zHtVqXt8k4yFyk2QGmgp48D9xZr2G94xWYnF4dak/e6bde80da922879ee5cf7d6d47960593_application.bin"
},
"metaData": {
"__type": "File",
"name": "9082ea4691359bee99f15aad763a4020_application.dat",
"url": "https://cloud.plejd.com/parse/files/zHtVqXt8k4yFyk2QGmgp48D9xZr2G94xWYnF4dak/9082ea4691359bee99f15aad763a4020_application.dat"
},
"version": "1.4.4",
"buildTime": 20231122114870,
"firmwareApi": "12",
"createdAt": "2023-11-24T09:17:18.850Z",
"updatedAt": "2023-11-24T09:17:18.850Z",
"ACL": {},
"objectId": "YP9uedNKVw",
"__type": "Object",
"className": "Firmware"
},
"createdAt": "2024-02-20T19:52:25.976Z",
"updatedAt": "2024-02-20T19:52:38.388Z",
"ACL": {},
"objectId": "C3xCAWEZTr",
"__type": "Object",
"className": "PlejdDevice"
}
siteData.inputSettings
:
{
"motionSensorData": {
"threshold": 50,
"blindTime": 15,
"windowTime": 0,
"pulseCounter": 0,
"requireZeroCrossing": true,
"useHpf04": false
},
"deviceId": "EE2FE8EBFE52",
"siteId": "<REDACTED>",
"input": 0,
"buttonType": "WirelessMotionSensor",
"dimSpeed": -1,
"doubleSidedDirectionButton": false,
"createdAt": "2024-02-20T19:52:26.120Z",
"updatedAt": "2024-02-20T19:58:49.184Z",
"ACL": {},
"objectId": "ud53ef9U0b",
"__type": "Object",
"className": "PlejdDeviceInputSetting"
}
siteData.motionSensors
(new section, list of objects):
{
"siteId": "<REDACTED>",
"deviceId": "EE2FE8EBFE52",
"input": 0,
"deviceParseId": "poSqxqgd8X",
"dirty": false,
"dirtyRemove": false,
"active": true,
"createdAt": "2024-02-20T19:53:04.706Z",
"updatedAt": "2024-02-20T19:53:04.706Z",
"ACL": {},
"objectId": "yXpj4rivza",
"__type": "Object",
"className": "MotionSensor"
}
It's also listed in siteData.inputAddress
and siteData.deviceAddress
as usual.
Broken out of #130
Thought it could be a good idea to compile all known BLE commands and their structure. From this projects code base, mentioned PR and some light looking @klali's great work in https://github.com/klali/ha-plejd
API has been documented in typings
.d.ts
files bound for the v0.8.0 release.Below are my best initial guesses/compilations. Please write any mistakes or improvements in the comments!
BLE characteristics
UUID:s for light level, data, last data, auth and ping below. Used to listen to incoming messages and write messages
Outgoing messages
Request light level update
Posted to
light level
characteristic. Value0x01
(hex). Response sent back on same characteristic. Response format:General format of incoming messages
List of fields below
BLE device Id
B82B1730526F
Command/request
0110
: Command (no response)0102
: Read (request response)0103
: Read?Command
Commands are 2 bytes, so 00 should be included.
Is the00
prefix actually part of the command? So: Is the command001b
or1b
? Does it matter which we parse?0021
: Scene trigger0097
: State update0098
: Dim+state update ("DIM2" in this code)0016
: (Wireless) button pressed001b
: Time broadcast00c8
: Dim+state update ("DIM" in this code)Data
Depending on command
Commands
Scene trigger
Command
0021
02
always in my installationState update
Command
0097
State/dim update
Command
0098
/00c8
00
so it can probably be discarded(Wireless) button pressed
Command
0016
button_1
button_2
button_3
button_4
button_1
Time
Command
001b
Using @emilohman's great explanation:
(btw, the above code is actually picked up by the lastest code restructure, but only logged with
Command 001b seems to be some kind of often repeating ping/mesh data
- now I know better, thanks @emilohman!)