diff --git a/README.md b/README.md index e0f246da2..915ebb257 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [256] ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill [257] BMW Gen3 TPMS [258] Chamberlain CWPIRC PIR Sensor - [259] ThermoPro TP829b Meat Thermometer 4 coated probes + [259] ThermoPro Meat Thermometers, TP829B 4 probes with temp only [260]* Arad/Master Meter Dialog3G water utility meter [261] Geevon TX16-3 outdoor sensor [262] Fine Offset Electronics WH46 air quality sensor @@ -352,6 +352,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [264] Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E [265] Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi [266] Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P + [267] ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI * Disabled by default, use -R n or a conf file to enable diff --git a/conf/rtl_433.example.conf b/conf/rtl_433.example.conf index b9d07a597..115d59c5a 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -485,7 +485,7 @@ convert si protocol 256 # ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill protocol 257 # BMW Gen3 TPMS protocol 258 # Chamberlain CWPIRC PIR Sensor - protocol 259 # ThermoPro TP829b Meat Thermometer 4 coated probes + protocol 259 # ThermoPro Meat Thermometers, TP829B 4 probes with temp only # protocol 260 # Arad/Master Meter Dialog3G water utility meter protocol 261 # Geevon TX16-3 outdoor sensor protocol 262 # Fine Offset Electronics WH46 air quality sensor @@ -493,6 +493,7 @@ convert si protocol 264 # Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E protocol 265 # Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi protocol 266 # Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P + protocol 267 # ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI ## Flex devices (command line option "-X") diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index ff783e724..875719fb3 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -274,6 +274,7 @@ DECL(arexx_ml) \ DECL(rosstech_dcu706) \ DECL(risco_agility) \ + DECL(thermopro_tp828b) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 831a9b23c..2d15403e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -237,7 +237,7 @@ add_library(r_433 STATIC devices/thermopro_tp11.c devices/thermopro_tp12.c devices/thermopro_tp28b.c - devices/thermopro_tp829b.c + devices/thermopro_tp82xb.c devices/thermopro_tx2.c devices/thermopro_tx2c.c devices/thermor.c diff --git a/src/devices/thermopro_tp829b.c b/src/devices/thermopro_tp829b.c deleted file mode 100644 index f64ceb5e7..000000000 --- a/src/devices/thermopro_tp829b.c +++ /dev/null @@ -1,138 +0,0 @@ -/** @file - ThermoPro TP829b Meat Thermometer four probes. - - Copyright (C) 2024 Bruno OCTAU (\@ProfBoc75) - - 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" - -/** -ThermoPro TP829b Meat Thermometer four probes. - -Issue #2961 open by \@AryehGielchinsky -Product web page : https://buythermopro.com/product/tp829/ - -Flex decoder: - - rtl_433 -X "n=tp829b,m=FSK_PCM,s=102,l=102,r=5500,preamble=552dd4" *.cu8 2>&1 | grep codes - - codes : {164}082f2efeddeddedde8d2d2d2d2d20000000000000 - -Data layout: - - Byte Position 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - II II 11 12 22 33 34 44 CC TT TT TT TT TT TT TT TT TT TT TT T - Sample d2 55 2d d4 08 2f 2e fe dd ed de dd e8 d2 d2 d2 d2 d2 00 00 00 00 00 00 0 - -- II: {16} Sensor ID, to be confirmed as the battery low not identified, -- 111:{12} Temp probe 1, °C, offset 500, scale 10, -- 222:{12} Temp probe 2, °C, offset 500, scale 10, -- 333:{12} Temp probe 3, °C, offset 500, scale 10, -- 444:{12} Temp probe 4, °C, offset 500, scale 10, -- CC: {8} Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x55, final XOR 00 -- TT: Trailed bytes, not used (always d2 d2 ...... 00 00 ). - -*/ -static int thermopro_tp829b_decode(r_device *decoder, bitbuffer_t *bitbuffer) -{ - uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success - 0x55, 0x2d, 0xd4}; - - uint8_t b[9]; - - if (bitbuffer->num_rows > 1) { - decoder_logf(decoder, 1, __func__, "Too many rows: %d", bitbuffer->num_rows); - return DECODE_FAIL_SANITY; - } - int msg_len = bitbuffer->bits_per_row[0]; - - if (msg_len > 260) { - decoder_logf(decoder, 1, __func__, "Packet too long: %d bits", msg_len); - return DECODE_ABORT_LENGTH; - } - - int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); - - if (offset >= msg_len) { - decoder_log(decoder, 1, __func__, "Sync word not found"); - return DECODE_ABORT_EARLY; - } - - if ((msg_len - offset ) < 96 ) { - decoder_logf(decoder, 1, __func__, "Packet too short: %d bits", msg_len); - return DECODE_ABORT_LENGTH; - } - - offset += sizeof(preamble_pattern) * 8; - bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 9 * 8); - - // checksum is a Galois bit reflect and byte reflect, gen 0x98, key 0x55, final XOR 00 - uint8_t b_reflect[8]; - for (int p = 7; p != -1; p += -1) - b_reflect[7 - p] = b[p]; - int checksum = lfsr_digest8(b_reflect, 8, 0x98, 0x55); - if (checksum != b[8]) { - decoder_logf(decoder, 1, __func__, "Checksum error, calculated %x, expected %x", checksum, b[8]); - return DECODE_FAIL_MIC; - } - - decoder_log_bitrow(decoder, 2, __func__, b, 72, "MSG"); - - int id = b[0]; - int display_u = (b[1] & 0xF0) >> 4; - int flags = b[1] & 0xF; - int p1_raw = b[2] << 4 | (b[3] & 0xF0) >> 4; - int p2_raw = (b[3] & 0x0F) << 8 | b[4]; - int p3_raw = b[5] << 4 | (b[6] & 0xF0) >> 4; - int p4_raw = (b[6] & 0x0F) << 8 | b[7]; - float p1_temp = (p1_raw - 500) * 0.1f; - float p2_temp = (p2_raw - 500) * 0.1f; - float p3_temp = (p3_raw - 500) * 0.1f; - float p4_temp = (p4_raw - 500) * 0.1f; - - /* clang-format off */ - data_t *data = data_make( - "model", "", DATA_STRING, "ThermoPro-TP829b", - "id", "", DATA_FORMAT, "%02x", DATA_INT, id, - "display_u", "Display Unit", DATA_COND, display_u == 0x2, DATA_STRING, "Fahrenheit", - "display_u", "Display Unit", DATA_COND, display_u == 0x0, DATA_STRING, "Celcius", - "temperature_1_C", "Temperature 1", DATA_COND, p1_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_temp, // if 0xedd then no probe - "temperature_2_C", "Temperature 2", DATA_COND, p2_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_temp, // if 0xedd then no probe - "temperature_3_C", "Temperature 3", DATA_COND, p3_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p3_temp, // if 0xedd then no probe - "temperature_4_C", "Temperature 4", DATA_COND, p4_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p4_temp, // if 0xedd then no probe - "flags", "Flags", DATA_FORMAT, "%01x", DATA_INT, flags, - "mic", "Integrity", DATA_STRING, "CHECKSUM", - NULL); - /* clang-format on */ - - decoder_output_data(decoder, data); - return 1; -} - -static char const *const output_fields[] = { - "model", - "id", - "display_u", - "temperature_1_C", - "temperature_2_C", - "temperature_3_C", - "temperature_4_C", - "flags", - "mic", - NULL, -}; - -r_device const thermopro_tp829b = { - .name = "ThermoPro TP829b Meat Thermometer 4 coated probes", - .modulation = FSK_PULSE_PCM, - .short_width = 102, - .long_width = 102, - .reset_limit = 5500, - .decode_fn = &thermopro_tp829b_decode, - .fields = output_fields, -}; diff --git a/src/devices/thermopro_tp82xb.c b/src/devices/thermopro_tp82xb.c new file mode 100644 index 000000000..e03f01064 --- /dev/null +++ b/src/devices/thermopro_tp82xb.c @@ -0,0 +1,291 @@ +/** @file + ThermoPro TP82xB Meat Thermometer probes. + + Copyright (C) 2024 Bruno OCTAU (\@ProfBoc75) + + 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. +*/ + +/* +ThermoPro TP82xB Meat Thermometer probes. + +Devices decoded: +- TP828P, 2 Probes, current Temperature, BBQ LO and HI temperatures +- TP829B, 4 Probes, simple Temperature +*/ + +#include "decoder.h" + +/** +ThermoPro TP828P 2 Probes. + +- Current Temperature of probes, BBQ Target LO and HI temperatures +- Issue #3082 open by Ryan Bray (\@rbray89) +- Product archive web page: http://web.archive.org/web/20240717222907/https://buythermopro.com/product/tp828w/ +- FCCID : https://fccid.io/2AATP-TP828B + +Flex decoder: + + rtl_433 -X "n=tp829b,m=FSK_PCM,s=102,l=102,r=5500,preamble=552dd4" *.cu8 2>&1 | grep codes + + codes: {164}772c2eceaa4f3eddeaa4d7b2d2d2d2d2d20000000 + +Data layout: + + Byte Position 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + II UF 11 11 11 11 12 22 22 22 22 CC TT TT TT TT TT TT TT TT T + PP PL LL HH HP PP LL LH HH + Sample d2 55 2d d4 77 2c 2e ce aa 4f 3e dd ea a4 d7 b2 d2 d2 d2 d2 d2 00 00 00 0 + +- II: {8} Sensor ID, +- U: {4} Temp Unit Display, 0x0 for Celsius, 0x2 for Fahrenheit, +- F: {4} Unknown flags, always 0xC, to be confirmed as the battery low not identified, +- 111/PPP:{12} Probe 1 Current Temp , °C, offset 500, scale 10, +- 111/LLL:{12} Probe 1 Target LO Temp, °C, offset 500, scale 10, +- 111/HHH:{12} Probe 1 Target HI Temp, °C, offset 500, scale 10, +- 222/PPP:{12} Probe 2 Current Temp, °C, offset 500, scale 10, +- 222/LLL:{12} Probe 2 Target LO Temp, °C, offset 500, scale 10, +- 222/HHH:{12} Probe 2 Target HI Temp, °C, offset 500, scale 10, +- CC: {8} Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x16, final XOR 0xac, +- TT: Trailed bytes, not used (always d2 d2 ...... 00 00 ). + +*/ +static int thermopro_tp828b_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success + 0x55, 0x2d, 0xd4}; + // Message len is 9 byte for tp829b and 12 byte for tp828b + uint8_t b[12]; + + if (bitbuffer->num_rows > 1) { + decoder_logf(decoder, 1, __func__, "Too many rows: %d", bitbuffer->num_rows); + return DECODE_FAIL_SANITY; + } + int msg_len = bitbuffer->bits_per_row[0]; + + if (msg_len > 280) { + decoder_logf(decoder, 1, __func__, "Packet too long: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + + int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); + + if (offset >= msg_len) { + decoder_log(decoder, 1, __func__, "Sync word not found"); + return DECODE_ABORT_EARLY; + } + + if ((msg_len - offset ) < 96 ) { + decoder_logf(decoder, 1, __func__, "Packet too short: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + + offset += sizeof(preamble_pattern) * 8; + bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 12 * 8); + + uint8_t b_reflect[11]; + for (int p = 10; p != -1; p += -1) + b_reflect[10 - p] = b[p]; + int checksum = lfsr_digest8(b_reflect, 11, 0x98, 0x16) ^ 0xac ; + if (checksum != b[11]) { + decoder_logf(decoder, 1, __func__, "Checksum error, calculated %x, expected %x", checksum, b[11]); + return DECODE_FAIL_MIC; + } + + decoder_log_bitrow(decoder, 2, __func__, b, 96, "MSG"); + + int id = b[0]; + int display_u = (b[1] & 0xF0) >> 4; + int flags = b[1] & 0xF; + int p1_raw = b[2] << 4 | (b[3] & 0xF0) >> 4; + int p1_lo_raw = (b[3] & 0x0F) << 8 | b[4]; + int p1_hi_raw = b[5] << 4 | (b[6] & 0xF0) >> 4; + int p2_raw = (b[6] & 0x0F) << 8 | b[7]; + int p2_lo_raw = b[8] << 4 | (b[9] & 0xF0) >> 4; + int p2_hi_raw = (b[9] & 0x0F) << 8 | b[10]; + + float p1_temp = (p1_raw - 500) * 0.1f; + float p1_t_lo = (p1_lo_raw - 500) * 0.1f; + float p1_t_hi = (p1_hi_raw - 500) * 0.1f; + float p2_temp = (p2_raw - 500) * 0.1f; + float p2_t_lo = (p2_lo_raw - 500) * 0.1f; + float p2_t_hi = (p2_hi_raw - 500) * 0.1f; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "ThermoPro-TP828b", + "id", "", DATA_FORMAT, "%02x", DATA_INT, id, + "display_u", "Display Unit", DATA_COND, display_u == 0x2, DATA_STRING, "Fahrenheit", + "display_u", "Display Unit", DATA_COND, display_u == 0x0, DATA_STRING, "Celsius", + "temperature_1_C", "Temperature 1", DATA_COND, p1_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_temp, // if 0xedd then no probe + "temperature_1_LO_C", "Temperature 1 LO", DATA_COND, p1_lo_raw != 0xeaa , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_t_lo, // if 0xeaa then no LO + "temperature_1_HI_C", "Temperature 1 HI", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_t_hi, + "temperature_2_C", "Temperature 2", DATA_COND, p2_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_temp, // if 0xedd then no probe + "temperature_2_LO_C", "Temperature 2 LO", DATA_COND, p2_lo_raw != 0xeaa , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_t_lo, // if 0xeaa then no LO + "temperature_2_HI_C", "Temperature 2 HI", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_t_hi, + "flags", "Flags", DATA_FORMAT, "%01x", DATA_INT, flags, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +/** +ThermoPro TP829B 4 Probes. + +- Current Temperature of probes only. +- Issue #2961 open by \@AryehGielchinsky +- Product web page : https://buythermopro.com/product/tp829/ + +Flex decoder: + + rtl_433 -X "n=tp829b,m=FSK_PCM,s=102,l=102,r=5500,preamble=552dd4" *.cu8 2>&1 | grep codes + + codes: {164}082f2efeddeddedde8d2d2d2d2d20000000000000 + +Data layout: + + Byte Position 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 + II UF 11 12 22 33 34 44 CC TT TT TT TT TT TT TT TT TT TT TT T + Sample d2 55 2d d4 08 2f 2e fe dd ed de dd e8 d2 d2 d2 d2 d2 00 00 00 00 00 00 0 + +- II: {8} Sensor ID, +- U: {4} Temp Unit Display, 0x0 for Celsius, 0x2 for Fahrenheit, +- F: {4} Unknown flags, always 0xF, to be confirmed as the battery low not identified, +- 111:{12} Temp probe 1, °C, offset 500, scale 10, +- 222:{12} Temp probe 2, °C, offset 500, scale 10, +- 333:{12} Temp probe 3, °C, offset 500, scale 10, +- 444:{12} Temp probe 4, °C, offset 500, scale 10, +- CC: {8} Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x55, final XOR 0x00, +- TT: Trailed bytes, not used (always d2 d2 ...... 00 00 ). + +*/ +static int thermopro_tp829b_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success + 0x55, 0x2d, 0xd4}; + + uint8_t b[9]; + + if (bitbuffer->num_rows > 1) { + decoder_logf(decoder, 1, __func__, "Too many rows: %d", bitbuffer->num_rows); + return DECODE_FAIL_SANITY; + } + int msg_len = bitbuffer->bits_per_row[0]; + + if (msg_len > 260) { + decoder_logf(decoder, 1, __func__, "Packet too long: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + + int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); + + if (offset >= msg_len) { + decoder_log(decoder, 1, __func__, "Sync word not found"); + return DECODE_ABORT_EARLY; + } + + if ((msg_len - offset ) < 96 ) { + decoder_logf(decoder, 1, __func__, "Packet too short: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + + offset += sizeof(preamble_pattern) * 8; + bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 9 * 8); + + // checksum is a Galois bit reflect and byte reflect, gen 0x98, key 0x55, final XOR 0x00 + uint8_t b_reflect[8]; + for (int p = 7; p != -1; p += -1) + b_reflect[7 - p] = b[p]; + int checksum = lfsr_digest8(b_reflect, 8, 0x98, 0x55); + if (checksum != b[8]) { + decoder_logf(decoder, 1, __func__, "Checksum error, calculated %x, expected %x", checksum, b[8]); + return DECODE_FAIL_MIC; + } + + decoder_log_bitrow(decoder, 2, __func__, b, 72, "MSG"); + + int id = b[0]; + int display_u = (b[1] & 0xF0) >> 4; + int flags = b[1] & 0xF; + int p1_raw = b[2] << 4 | (b[3] & 0xF0) >> 4; + int p2_raw = (b[3] & 0x0F) << 8 | b[4]; + int p3_raw = b[5] << 4 | (b[6] & 0xF0) >> 4; + int p4_raw = (b[6] & 0x0F) << 8 | b[7]; + float p1_temp = (p1_raw - 500) * 0.1f; + float p2_temp = (p2_raw - 500) * 0.1f; + float p3_temp = (p3_raw - 500) * 0.1f; + float p4_temp = (p4_raw - 500) * 0.1f; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "ThermoPro-TP829b", + "id", "", DATA_FORMAT, "%02x", DATA_INT, id, + "display_u", "Display Unit", DATA_COND, display_u == 0x2, DATA_STRING, "Fahrenheit", + "display_u", "Display Unit", DATA_COND, display_u == 0x0, DATA_STRING, "Celsius", + "temperature_1_C", "Temperature 1", DATA_COND, p1_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_temp, // if 0xedd then no probe + "temperature_2_C", "Temperature 2", DATA_COND, p2_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_temp, // if 0xedd then no probe + "temperature_3_C", "Temperature 3", DATA_COND, p3_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p3_temp, // if 0xedd then no probe + "temperature_4_C", "Temperature 4", DATA_COND, p4_raw != 0xedd , DATA_FORMAT, "%.1f C", DATA_DOUBLE, p4_temp, // if 0xedd then no probe + "flags", "Flags", DATA_FORMAT, "%01x", DATA_INT, flags, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +static char const *const tp828b_output_fields[] = { + "model", + "id", + "display_u", + "temperature_1_C", + "temperature_1_LO_C", + "temperature_1_HI_C", + "temperature_2_C", + "temperature_2_LO_C", + "temperature_2_HI_C", + "flags", + "mic", + NULL, +}; + +r_device const thermopro_tp828b = { + .name = "ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI", + .modulation = FSK_PULSE_PCM, + .short_width = 102, + .long_width = 102, + .reset_limit = 1500, + .decode_fn = &thermopro_tp828b_decode, + .priority = 10, // let decode first the tp829b as the message is shorter + .fields = tp828b_output_fields, +}; + +static char const *const tp829b_output_fields[] = { + "model", + "id", + "display_u", + "temperature_1_C", + "temperature_2_C", + "temperature_3_C", + "temperature_4_C", + "flags", + "mic", + NULL, +}; + +r_device const thermopro_tp829b = { + .name = "ThermoPro Meat Thermometers, TP829B 4 probes with temp only", + .modulation = FSK_PULSE_PCM, + .short_width = 102, + .long_width = 102, + .reset_limit = 1500, + .decode_fn = &thermopro_tp829b_decode, + .fields = tp829b_output_fields, +};