Closed aadnehovda closed 1 year ago
Current conf-file I'm using:
sample_rate 2M
decoder {
name=WattsThermostat,
modulation=OOK_PWM,
short=260,
long=600,
sync=6000,
gap=0,
bits=54,
reset=900,
match={8}5a,
get=@8:{16}:id,
get=@24:{1}:pairing
}
Indeed looks like PWM: we observe 4 pulse/gap-symbols: long/long, long/short, short/short, short/long -- but when looking at gap/pulse-symbols: long/short, short/long only.
The pulse sample is {62} 5A C5 89 F1 F0 5A FC
but the BitBench has shorter codes (54) -- can you check that?
The possible checksum looks roughly XOR-based, but we need to be sure about alignment and size to inspect it. Then bitbrk from revdgst can uncover a relation given enough codes.
The pulse sample is
{62} 5A C5 89 F1 F0 5A FC
but the BitBench has shorter codes (54) -- can you check that?
I noticed that too, but it only shows up as 62 bits on the web page (where the last 8 bits are always 0). The incoming data is always 54 bits.
Here's a sample from the analyzer:
2022-11-07 15:26:34,,{54}5ab249f5f79a54,WattsThermostat,1,1,
Analyzing pulses...
Total count: 55, width: 53.24 ms (106475 S)
Pulse width distribution:
[ 0] count: 1, width: 6238 us [6238;6238] (12475 S)
[ 1] count: 23, width: 632 us [630;645] (1264 S)
[ 2] count: 31, width: 286 us [284;296] ( 572 S)
Gap width distribution:
[ 0] count: 23, width: 238 us [238;240] ( 477 S)
[ 1] count: 31, width: 584 us [582;585] (1167 S)
Pulse period distribution:
[ 0] count: 1, width: 6475 us [6475;6475] (12950 S)
[ 1] count: 17, width: 1216 us [1214;1225] (2431 S)
[ 2] count: 16, width: 525 us [524;534] (1050 S)
[ 3] count: 20, width: 870 us [869;884] (1740 S)
Pulse timing distribution:
[ 0] count: 1, width: 6238 us [6238;6238] (12475 S)
[ 1] count: 54, width: 604 us [582;645] (1208 S)
[ 2] count: 54, width: 266 us [238;296] ( 531 S)
[ 3] count: 1, width: 62376 us [62376;62376] (124751 S)
Level estimates [high, low]: 7572, 119
RSSI: -3.4 dB SNR: 18.0 dB Noise: -21.4 dB
Frequency offsets [F1, F2]: 7148, 0 (+218.1 kHz, +0.0 kHz)
Guessing modulation: Pulse Width Modulation with sync/delimiter
view at https://triq.org/pdv/#AAB104185D025C0109F3A78291A291A1A291A291A291A1A29291A29291A29291A29291A1A1A1A1A291A291A1A1A1A1A291A1A1A1A29291A1A291A29291A291A291A355
Attempting demodulation... short_width: 286, long_width: 632, reset_limit: 586, sync_width: 6238
Use a flex decoder with -X 'n=name,m=OOK_PWM,s=286,l=632,r=586,g=0,t=0,y=6238'
pulse_demod_pwm(): Analyzer Device
bitbuffer:: Number of rows: 1
[00] {54} 5a b2 49 f5 f7 9a 54
I find it weird that there are so many bits changing between samples when I only adjust the set-point wheel, I would have thought some kind of pattern emerge, but I can't really see it. Could the payload be double-encoded somehow?
I turned on and put one device inside the refrigerator and captured the data, including timestamps. This should provide some data about changing thermistor-values only, without any changes to the set-point.
2022-11-07 15:39:14,,{54}5ab2497ef39894,WattsThermostat,1,1,
2022-11-07 15:39:15,,{54}5ab24971f39a94,WattsThermostat,1,1,
2022-11-07 15:39:15,,{54}5ab24971f79994,WattsThermostat,1,1,
2022-11-07 15:39:16,,{54}5ab24971f79994,WattsThermostat,1,1,
2022-11-07 15:39:16,,{54}5ab249f1f79b94,WattsThermostat,1,1,
2022-11-07 15:39:19,,{54}5ab249f1f79b94,WattsThermostat,1,1,
2022-11-07 15:39:22,,{54}5ab249f9f79854,WattsThermostat,1,1,
2022-11-07 15:39:25,,{54}5ab249f5f79a54,WattsThermostat,1,1,
2022-11-07 15:39:37,,{54}5ab249f68f998c,WattsThermostat,1,1,
2022-11-07 15:39:39,,{54}5ab249f98f9a4c,WattsThermostat,1,1,
2022-11-07 15:39:42,,{54}5ab249f58b9a4c,WattsThermostat,1,1,
2022-11-07 15:39:45,,{54}5ab249fb8f9acc,WattsThermostat,1,1,
2022-11-07 15:39:51,,{54}5ab249f44f992c,WattsThermostat,1,1,
2022-11-07 15:39:54,,{54}5ab249f24b9b2c,WattsThermostat,1,1,
2022-11-07 15:39:57,,{54}5ab249fa4f9aac,WattsThermostat,1,1,
2022-11-07 15:40:00,,{54}5ab249fe4f9bac,WattsThermostat,1,1,
2022-11-07 15:40:08,,{54}5ab249f34f98ec,WattsThermostat,1,1,
2022-11-07 15:40:11,,{54}5ab249fb4f9aec,WattsThermostat,1,1,
2022-11-07 15:40:14,,{54}5ab249f8cf9a1c,WattsThermostat,1,1,
2022-11-07 15:40:17,,{54}5ab249f4cf991c,WattsThermostat,1,1,
2022-11-07 15:40:26,,{54}5ab249f1cf985c,WattsThermostat,1,1,
2022-11-07 15:40:29,,{54}5ab249f9cf9a5c,WattsThermostat,1,1,
2022-11-07 15:40:32,,{54}5ab249f9cf9a5c,WattsThermostat,1,1,
2022-11-07 15:40:40,,{54}5ab249f7cf99dc,WattsThermostat,1,1,
2022-11-07 15:40:43,,{54}5ab249ffcf9bdc,WattsThermostat,1,1,
2022-11-07 15:40:49,,{54}5ab249fc2f9b3c,WattsThermostat,1,1,
2022-11-07 15:40:52,,{54}5ab249fa2b98bc,WattsThermostat,1,1,
2022-11-07 15:40:54,,{54}5ab249f62b9abc,WattsThermostat,1,1,
2022-11-07 15:40:57,,{54}5ab249fe2f9bbc,WattsThermostat,1,1,
2022-11-07 15:41:00,,{54}5ab249f92b987c,WattsThermostat,1,1,
2022-11-07 15:41:03,,{54}5ab249f52f997c,WattsThermostat,1,1,
2022-11-07 15:41:12,,{54}5ab249f32f98fc,WattsThermostat,1,1,
2022-11-07 15:41:14,,{54}5ab249fb2f9afc,WattsThermostat,1,1,
2022-11-07 15:41:17,,{54}5ab249ff2f9bfc,WattsThermostat,1,1,
2022-11-07 15:41:26,,{54}5ab249fcaf9b00,WattsThermostat,1,1,
2022-11-07 15:41:29,,{54}5ab249f2ab9b00,WattsThermostat,1,1,
2022-11-07 15:41:31,,{54}5ab249faaf9a80,WattsThermostat,1,1,
2022-11-07 15:41:34,,{54}5ab249f6af9980,WattsThermostat,1,1,
2022-11-07 15:41:37,,{54}5ab249f1ab9b80,WattsThermostat,1,1,
2022-11-07 15:41:43,,{54}5ab249f9ab9840,WattsThermostat,1,1,
2022-11-07 15:41:46,,{54}5ab249f5ab9a40,WattsThermostat,1,1,
2022-11-07 15:41:48,,{54}5ab249fdab9940,WattsThermostat,1,1,
2022-11-07 15:41:51,,{54}5ab249f3ab9b40,WattsThermostat,1,1,
2022-11-07 15:41:54,,{54}5ab249fbaf9ac0,WattsThermostat,1,1,
2022-11-07 15:41:57,,{54}5ab249ffab99c0,WattsThermostat,1,1,
2022-11-07 15:42:00,,{54}5ab249f06b9bc0,WattsThermostat,1,1,
2022-11-07 15:42:05,,{54}5ab249fc6b9920,WattsThermostat,1,1,
2022-11-07 15:42:08,,{54}5ab249fc6b9920,WattsThermostat,1,1,
2022-11-07 15:42:25,,{54}5ab249f96f9a60,WattsThermostat,1,1,
2022-11-07 15:42:28,,{54}5ab249fd6b9960,WattsThermostat,1,1,
2022-11-07 15:42:31,,{54}5ab249f56b9a60,WattsThermostat,1,1,
2022-11-07 15:42:33,,{54}5ab249f36b9b60,WattsThermostat,1,1,
2022-11-07 15:42:36,,{54}5ab249fb6b98e0,WattsThermostat,1,1,
2022-11-07 15:42:39,,{54}5ab249f76f99e0,WattsThermostat,1,1,
2022-11-07 15:42:47,,{54}5ab249f8eb9810,WattsThermostat,1,1,
2022-11-07 15:42:50,,{54}5ab249f8ef9a10,WattsThermostat,1,1,
2022-11-07 15:42:53,,{54}5ab249f8eb9810,WattsThermostat,1,1,
2022-11-07 15:42:59,,{54}5ab249f2eb9b10,WattsThermostat,1,1,
2022-11-07 15:43:01,,{54}5ab249faeb9890,WattsThermostat,1,1,
2022-11-07 15:43:04,,{54}5ab249faef9a90,WattsThermostat,1,1,
2022-11-07 15:44:57,,{54}5ab249f49f9908,WattsThermostat,1,1,
2022-11-07 15:46:49,,{54}5ab249fa5f9aa8,WattsThermostat,1,1,
2022-11-07 15:48:40,,{54}5ab249f8df9a18,WattsThermostat,1,1,
2022-11-07 15:50:30,,{54}5ab249f5df9958,WattsThermostat,1,1,
Was going to suggest that, slowly changing just the temperature so we can observe where that field is.
If you use just v
as format in BitBench you can make out the temperature field in the middle and the checksum at the end.
Exactly the last 8 bit do change but not random, looks like a counter. The 8 bits before that never change, the 10 bits before that do change but the pattern there is not clear.
I'd guess LSB/MSB are reversed, see this BitBench.
Wow, that's good progress. I noticed I had experimented with inverting the data and left it flipped in the bitbench. When I flip it back it indeed counts from 0-255 and rolls over.
Also the "10 bit" field does count down, if we put the last two bits somewhere(?) else:
SYN?8h ID:16h FLAGS?4b TEMP?^8d 2b SETP?^8h ^CHK?8d
For the two extra temp bits the left(earlier) one seems to be either a sign or MSB, the right one seems to be the temp LSB. The checksum is not by byte but by "field", note that it is in step (1 unit count) with all of: 8-bit temp field, announce flag, the temp LSB bit. Not sure where the temp sign/MSB or the other flags go into the checksum though.
Okay, so perhaps it makes sense to keep the data inverted. I noticed this text in the datasheet for the receiver. The analyzer defaults to long pulse => logic 0, while the test instructions for the chip has it the other way. I'd guess the people implementing the protocol just picked the same encoding as in the datasheet. At least the temp readings look better that way (as you so quickly found :-), and the counter could very well be designed to decrement.
There is no common understanding how symbols should map to 0/1. In rtl_433 we take the arbitrary (but easy to mnemonic) rule: low frequency (slow change, long symbol) -> 0
and high frequency (fast change, short symbol) -> 1
.
With "counter" it just wanted to express "counting through a range of values". I don't think it's a counter but rather the temperature value. The data series was the sensor cooling down, right? Otherwise the field should be "counting" up rather...
I see, makes sense. Yes, that data stream should be from around 25C to around 4C-6C which seems correct. The set-point seems to be upside down, i.e. the numbers decrease as I increase the setpoint temp. The range printed on the case is 5C -> 30C. I've added some more samples, with comments, to bitbench, including a few from a device inside the freezer (to possibly test for sign) but not sure the device supports subzero measurements, seems to stop at 0C.
Looks like the temp field is 9-bit then.
SYN?8h ID:16h FLAGS?4b TEMP?^9d SETP?^9d ^CHK?8d
And the other bit in the middle then could be the setpoint LSB -- at least it very much changes with that field.
Use SETP?^9b
and watch it work nicely in e.g. "Took it out from the freezer".
Could be a coincidence but temp looks like 1/10 C, right?
Yes, 9bits for the temps looks correct! I think it's pretty normal for such values to have a factor of 10 and it matches pretty well.
By the way, is there a way to reverse LSB/MSB in a flex-decoder defined in a conf-file? Was looking for a way to "get" the values directly. Browsing through flex.c I cannot find an option to do that. I tried adding negative length for the field, but that ended in a segfault :-).
Here is my current conf-file:
# Need at least 1M sample rate (maybe 0.5M is enough)
sample_rate 2M
decoder {
name=WattsThermostat,
modulation=OOK_PWM,
short=260,
long=600,
sync=6000,
gap=0,
bits=54,
reset=900,
match={8}5a,
get = @0:{8}:syn,
get = @8:{16}:id,
get = @24:{4}:%X:flags,
get = @28:{9}:temp,
get = @37:{9}:setpoint,
get = @46:{8}:%X:chk
}
Sorry, the getters are not that sophisticated. But to verify the checksum I would recommand a custom decoder anyway. For testing maybe send the raw-ish output to a python script. Should be the easiest option.
It looks as if all the fields could be LSB/MSB swapped. ID:^16d
resolves to an ID matching a five digit number which is hand written directly on the PCB (some poor guy had to sit down and program each device one by one. :-D)
The preamble is the same bitpattern both LSB/MSB so it does not matter.
For the checksum it looks as if all fields are included as you mentioned and that it is some sort of SUM.
(I added invert to the flex-conf during capture on the following data series instead of setting it in bitbench)
Very nice. If you try adding the checksum on the larger dataset there are minor problems though -- some rows differ by 1, some bit is still off.
FYI, my system is a somewhat older OEM system with a different brand name, but I found a current product which I assume is compatible with the protocol. WATTS WFHT-RF Basic (P01857). The PCB looks kind of similar, or at least like it evolved.
Theirs: (OF: 252063, some revision number?)
Mine: (OF: 221439, printed on the box)
I adapted some code, let me know if this should go in a PR or whether chksum validation is required first.
/** @file
Watts WFHT-RF Thermostat
Copyright (C) 2022 Ådne Hovda <aadne@hovda.no>
based on protocol decoding by Christian W. Zuckschwerdt <zany@triq.net>
and Ådne Hovda <aadne@hovda.no>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
#include "decoder.h"
/**
Watts WFHT-RF Thermostat
This code is based on a slightly older OEM system created by ADEV in France which
later merged with Watts. The closest thing currently available seems to be
https://wattswater.eu/catalog/regulation-and-control/radio-wfht-thermostats/electronic-room-thermostat-with-rf-control-wfht-rf-basic/,
but it is not known whether they are protocol compatible.
https://github.com/merbanan/rtl_433/issues/2230
TODO: chksum
Analyzer output:
Analyzing pulses...
Total count: 55, width: 337.75 ms (675502 S)
Pulse width distribution:
[ 0] count: 1, width: 291096 us [291096;291096] (582191 S)
[ 1] count: 26, width: 630 us [630;640] (1260 S)
[ 2] count: 28, width: 288 us [286;302] ( 576 S)
Gap width distribution:
[ 0] count: 26, width: 233 us [232;234] ( 466 S)
[ 1] count: 28, width: 576 us [576;576] (1151 S)
Pulse period distribution:
[ 0] count: 1, width: 291328 us [291328;291328] (582657 S)
[ 1] count: 15, width: 1206 us [1205;1216] (2412 S)
[ 2] count: 14, width: 522 us [520;530] (1043 S)
[ 3] count: 24, width: 864 us [862;877] (1727 S)
Pulse timing distribution:
[ 0] count: 1, width: 291096 us [291096;291096] (582191 S)
[ 1] count: 54, width: 602 us [576;640] (1204 S)
[ 2] count: 54, width: 262 us [232;302] ( 523 S)
[ 3] count: 1, width: 100000 us [100000;100000] (200001 S)
Level estimates [high, low]: 15961, 150
RSSI: -0.1 dB SNR: 20.3 dB Noise: -20.4 dB
Frequency offsets [F1, F2]: 17869, 0 (+545.3 kHz, +0.0 kHz)
Guessing modulation: Pulse Width Modulation with sync/delimiter
view at https://triq.org/pdv/#AAB104FFFF025A0105FFFF8291A291A1A291A29291A29291A291A1A1A2929291A29291A1A1A1A1A29291A291A1A1A1A291A1A29291A1A29291A29291A1A2929291A355
Attempting demodulation... short_width: 288, long_width: 630, reset_limit: 577, sync_width: 291096
Use a flex decoder with -X 'n=name,m=OOK_PWM,s=288,l=630,r=577,g=0,t=0,y=291096'
pulse_slicer_pwm(): Analyzer Device
bitbuffer:: Number of rows: 1
[00] {54} 5a 4b 89 f2 f6 64 c4
Data Layout:
10100101 1011010001110110 1000 100100001 000011000 10101011
preamble id flags temp setpoint chksum
All fields need reflection, possibly easier to just reverse the whole
row first. The only flag found is PAIRING (0b0001). Chksum seems to be
additive.
Raw data:
{54}5ab24971f79994
{54}5ab24971f79994
{54}5ab249f1f79b94
{54}5ab249f1f79b94
{54}5ab249f9f79854
{54}5ab249f5f79a54
{54}5ab249f68f998c
{54}5ab249f98f9a4c
{54}5ab249f58b9a4c
{54}5ab249fb8f9acc
https://tinyurl.com/2z5jtfuu
Format string:
ID:^16d FLAGS:^4b TEMP:^9d SETP:^9d CHK:^8d
Decoded example:
ID:28205 FLAGS:0001 TEMP:265 SETP:048 CHK:214
*/
static int watts_thermostat_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
int ret = 0;
if (bitbuffer->num_rows != 1) {
decoder_logf(decoder, 2, __func__, "Only expect a single row. num_rows=%d", bitbuffer->num_rows);
ret = DECODE_ABORT_EARLY;
return ret;
}
uint8_t const preamble_pattern = 0xa5;
for (unsigned row = 0; row < bitbuffer->num_rows; ++row) {
// we expect 54 bits
if (bitbuffer->bits_per_row[row] != 54) {
decoder_log(decoder, 2, __func__, "Length check fail");
ret = DECODE_ABORT_LENGTH;
continue;
}
bitbuffer_invert(bitbuffer);
unsigned pos = bitbuffer_search(bitbuffer, row, 0, &preamble_pattern, 8);
if (pos >= bitbuffer->bits_per_row[row]) {
decoder_log(decoder, 2, __func__, "Preamble not found");
ret = DECODE_ABORT_EARLY;
continue;
}
decoder_logf(decoder, 2, __func__, "Found row: %d", row);
pos += 8;
uint8_t id_raw[2] = {0};
bitbuffer_extract_bytes(bitbuffer, row, pos, id_raw, 16);
unsigned id = (reverse8((id_raw[1])) << 8) | reverse8((id_raw[0]));
pos += 16;
uint8_t PAIRING = 0b0001;
uint8_t flags = {0};
bitbuffer_extract_bytes(bitbuffer, row, pos, &flags, 4);
flags = reverse8(flags);
unsigned pairing = flags & PAIRING;
pos += 4;
uint8_t temp_raw[2] = {0};
bitbuffer_extract_bytes(bitbuffer, row, pos, temp_raw, 9);
unsigned temp = (reverse8((temp_raw[1])) << 8) | reverse8((temp_raw[0]));
pos += 9;
uint8_t setp_raw[2] = {0};
bitbuffer_extract_bytes(bitbuffer, row, pos, setp_raw, 9);
unsigned setp = (reverse8((setp_raw[1])) << 8) | reverse8((setp_raw[0]));
pos += 9;
uint8_t chk_raw = {0};
bitbuffer_extract_bytes(bitbuffer, row, pos, &chk_raw, 8);
unsigned chk = reverse8(chk_raw);
//// verify checksum
// if ((add_bytes(b, 31) & 0xff) != b[31]) {
// decoder_log(decoder, 2, __func__, "Checksum fail");
// ret = DECODE_FAIL_MIC;
// continue;
// }
/* clang-format off */
data_t *data = data_make(
"model", "Model", DATA_STRING, "Watts WFHT-RF Thermostat",
"id", "ID", DATA_INT, id,
"pairing", "Pairing", DATA_COND, pairing, DATA_INT, pairing,
"temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp * 0.1f,
"setpoint_C", "Setpoint", DATA_FORMAT, "%.1f C", DATA_DOUBLE, setp * 0.1f,
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
return ret;
}
static char *output_fields[] = {
"model",
"id",
"pairing",
"temperature_C",
"setpoint_C",
NULL,
};
r_device watts_thermostat = {
.name = "Watts WFHT-RF Thermostat",
.modulation = OOK_PULSE_PWM,
.short_width = 260,
.long_width = 600,
.sync_width = 6000,
.reset_limit = 900,
.decode_fn = &watts_thermostat_decode,
.fields = output_fields,
};
Looks good. The first line after /**
needs a dot (.
) a the end.
You can start a PR and then add more commits as you go along. We always squash commits on merge.
So I think I've figured out the checksum. It's a 1 byte sum of all the decoded fields except the preamble, but each value is chopped up into max 1 byte pieces. So the id(16b), temp(9b) and setpoint(9b) are split into high and low bytes.
chksum = ((id >> 8) + (id & 0xFF) + flags + (temp >> 8) + (temp & 0xFF) + (setp >> 8) + (setp & 0xFF)) & 0xFF
@aadnehovda Status? Looks like you made a huge amount of progress and would be great to have it in the codebase.
My take is that decoders without enough checksums have to be not on by default, but can be in the codebase. Others will surely comment if you file a PR though.
Thanks for the reminder! :-)
I have a 12 zone underfloor heating system with wireless thermostats which each have a wheel for setting temperature and a thermistor for sensing the current temp. MASTER-RAIL NY.PDF
The data is PWM encoded with a duty cycle of approx. 1/3, which is correctly detected by the analyzer. Here shown in pulseview:
Another sample: https://triq.org/pdv/#AAB1041850025C0108F3208291A291A1A291A291A1A2929291A291A1A2929291A29291A1A1A1A1A2929291A1A1A1A1A29292929291A291A1A291A291A1A1A1A1A1A355
I have started to analyze the data received from the sensors and found the ID and the ANNOUNCE flag (used for pairing with the controller). After power on the thermostat quickly sends 5 packets, then settles down to a slower pace, I think perhaps the pace is also dependent on the current temp versus the set-point, but I'm not sure.
I expect at least the current temp and the set point, but maybe not in real units like C or F, since the controller does not really need it. Also maybe some checksum. The following bitbench is a power on of a single thermostat and then a gradual sweep of the set-point wheel from min to max. All performed within around 30 seconds, so the sensor value probably did not change much.
http://triq.net/bitbench#c=%7B54%7D5ab24970f799e4&c=%7B54%7D5ab24978f79be4&c=%7B54%7D5ab24978f79be4&c=%7B54%7D5ab24978f79be4&c=%7B54%7D5ab249f8f79814&c=%7B54%7D5ab249f8f79814&c=%7B54%7D5ab249f8f686ec&c=%7B54%7D5ab249f0f01620&c=%7B54%7D5ab249ff707408&c=%7B54%7D5ab249f8f07508&c=%7B54%7D5ab249f8f0cd58&c=%7B54%7D5ab249f0f0ce58&c=%7B54%7D5ab249f0f25f14&c=%7B54%7D5ab249f8f65e94&f=ID%3Ahhhh%20ANNOUNCE%3Ab%20bbbbbbbbbbbbbbbbbbbbbbbbbbbbb%0A&a=Preamble&m=5a&i=true&co=24&cl=22&cw=2