Add support for Watts WFHT-RF thermostat ()

This commit is contained in:
Ådne Hovda 2024-03-01 12:12:51 +01:00 committed by GitHub
parent 8d1541bde0
commit e2f5c8e498
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 194 additions and 0 deletions

View file

@ -338,6 +338,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
[250] Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge
[251] Fine Offset / Ecowitt WH55 water leak sensor
[252] BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata
[253] Watts WFHT-RF Thermostat
* Disabled by default, use -R n or a conf file to enable

View file

@ -479,6 +479,7 @@ convert si
protocol 250 # Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge
protocol 251 # Fine Offset / Ecowitt WH55 water leak sensor
protocol 252 # BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata
protocol 253 # Watts WFHT-RF Thermostat
## Flex devices (command line option "-X")

View file

@ -260,6 +260,7 @@
DECL(schou_72543_rain) \
DECL(fineoffset_wh55) \
DECL(tpms_bmw) \
DECL(watts_thermostat) \
/* Add new decoders here. */

View file

@ -254,6 +254,7 @@ add_library(r_433 STATIC
devices/vaillant_vrt340f.c
devices/vauno_en8822c.c
devices/visonic_powercode.c
devices/watts_thermostat.c
devices/waveman.c
devices/wec2103.c
devices/wg_pb12v1.c

View file

@ -0,0 +1,190 @@
/** @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"
/** @fn int watts_thermostat_decode(r_device *decoder, bitbuffer_t *bitbuffer)
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.
Modulation is PWM with preceeding gap. There is a very long lead-in pulse.
Symbols are ~260 us gap + ~600 us pulse and ~600 us gap + ~260 us pulse.
Bits are inverted and reflected.
Example Data:
10100101 1011010001110110 1000 100100001 000011000 10101011
preamble id flags temp setpoint chksum
Data Layout:
PP II II F .TT .SS XX
- P: (8-bit reflected) Preamble
- I: (16-bit reflected) ID
- F: (4-bit reflected) Flags
- T: (9-bit reflected) Temperature
- S: (9-bit reflected) Set-Point
- X: (8-bit reflected) Checksum (8-bit sum)
The only flag found is PAIRING (0b0001). Chksum is calculated by summing all
high and low bytes the for ID, Flags, Temperature and Set-Point.
Temperature and Set-Point values are in 0.1°C steps with an observed Set-Point
range of ~4°C to ~30°C.
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/wattsthermobitbench
Format string:
PRE:^8h ID:^16d FLAGS:^4b TEMP:^9d SETP:^9d CHK:^8d
Decoded example:
PRE:a5 ID:28082 FLAGS:0001 TEMP:271 SETP:304 CHK:097
PRE:a5 ID:28252 FLAGS:0000 TEMP:019 SETP:303 CHK:013
*/
#define WATTSTHERMO_BITLEN 54
#define WATTSTHERMO_PREAMBLE_BITLEN 8
#define WATTSTHERMO_ID_BITLEN 16
#define WATTSTHERMO_FLAGS_BITLEN 4
#define WATTSTHERMO_TEMPERATURE_BITLEN 9
#define WATTSTHERMO_SETPOINT_BITLEN 9
#define WATTSTHERMO_CHKSUM_BITLEN 8
enum WATTSTHERMO_FLAGS {
WF_NONE = 0,
WF_PAIRING = 1,
WF_UNKNOWN1 = 2,
WF_UNKNOWN2 = 4,
WF_UNKNOWN3 = 8,
};
static int watts_thermostat_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
uint8_t const preamble_pattern[] = {0xa5}; // inverted and reflected, raw value is 0x5a
bitbuffer_invert(bitbuffer);
// We're expecting a single row
for (uint16_t row = 0; row < bitbuffer->num_rows; ++row) {
uint16_t row_len = bitbuffer->bits_per_row[row];
unsigned bitpos = 0;
bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, WATTSTHERMO_PREAMBLE_BITLEN);
if (bitpos >= row_len) {
decoder_log(decoder, 2, __func__, "Preamble not found");
return DECODE_ABORT_EARLY;
}
if (row_len < WATTSTHERMO_BITLEN) {
decoder_log(decoder, 2, __func__, "Message too short");
return DECODE_ABORT_LENGTH;
}
bitpos += WATTSTHERMO_PREAMBLE_BITLEN;
uint8_t id_raw[2];
bitbuffer_extract_bytes(bitbuffer, row, bitpos, id_raw, WATTSTHERMO_ID_BITLEN);
reflect_bytes(id_raw, 2);
int id = (id_raw[1] << 8) | id_raw[0];
bitpos += WATTSTHERMO_ID_BITLEN;
uint8_t flags[1];
bitbuffer_extract_bytes(bitbuffer, row, bitpos, flags, WATTSTHERMO_FLAGS_BITLEN);
reflect_bytes(flags, 1);
int pairing = flags[0] & WF_PAIRING;
bitpos += WATTSTHERMO_FLAGS_BITLEN;
uint8_t temp_raw[2];
bitbuffer_extract_bytes(bitbuffer, row, bitpos, temp_raw, WATTSTHERMO_TEMPERATURE_BITLEN);
reflect_bytes(temp_raw, 2);
int temp = (temp_raw[1] << 8) | temp_raw[0];
bitpos += WATTSTHERMO_TEMPERATURE_BITLEN;
uint8_t setp_raw[2];
bitbuffer_extract_bytes(bitbuffer, row, bitpos, setp_raw, WATTSTHERMO_SETPOINT_BITLEN);
reflect_bytes(setp_raw, 2);
int setp = (setp_raw[1] << 8) | setp_raw[0];
bitpos += WATTSTHERMO_SETPOINT_BITLEN;
uint8_t chksum = add_bytes(id_raw, 2)
+ add_bytes(flags, 1)
+ add_bytes(temp_raw, 2)
+ add_bytes(setp_raw, 2);
uint8_t chk[1];
bitbuffer_extract_bytes(bitbuffer, row, bitpos, chk, WATTSTHERMO_CHKSUM_BITLEN);
reflect_bytes(chk, 1);
if (chk[0] != chksum) {
decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, "Checksum fail");
return DECODE_FAIL_MIC;
}
/* clang-format off */
data_t *data = data_make(
"model", "Model", DATA_STRING, "Watts-WFHTRF",
"id", "ID", DATA_INT, id,
"pairing", "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,
"flags", "Flags", DATA_INT, flags[0],
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
return 0;
}
static char const *const output_fields[] = {
"model",
"id",
"pairing",
"temperature_C",
"setpoint_C",
"flags",
"mic",
NULL,
};
r_device const 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,
};