Add support for Watts WFHT-RF thermostat (#2648)
This commit is contained in:
parent
8d1541bde0
commit
e2f5c8e498
5 changed files with 194 additions and 0 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -260,6 +260,7 @@
|
|||
DECL(schou_72543_rain) \
|
||||
DECL(fineoffset_wh55) \
|
||||
DECL(tpms_bmw) \
|
||||
DECL(watts_thermostat) \
|
||||
|
||||
/* Add new decoders here. */
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
190
src/devices/watts_thermostat.c
Normal file
190
src/devices/watts_thermostat.c
Normal 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,
|
||||
};
|
Loading…
Add table
Reference in a new issue