From c572b6ce1344b05883827a55ff651f1493cc05b5 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Mon, 25 Mar 2024 14:39:37 +0100 Subject: [PATCH 01/11] Add Support for Thermor DG950 Weather Station --- README.md | 1 + include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/thermor.c | 223 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 src/devices/thermor.c diff --git a/README.md b/README.md index 123f5471e..48f422f36 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [251] Fine Offset / Ecowitt WH55 water leak sensor [252] BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata [253] Watts WFHT-RF Thermostat + [254] Thermor DG950 Weather Station * Disabled by default, use -R n or a conf file to enable diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 80419954f..20bc930da 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -261,6 +261,7 @@ DECL(fineoffset_wh55) \ DECL(tpms_bmw) \ DECL(watts_thermostat) \ + DECL(thermor) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40869dbca..56c670024 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -230,6 +230,7 @@ add_library(r_433 STATIC devices/thermopro_tp12.c devices/thermopro_tx2.c devices/thermopro_tx2c.c + devices/thermor.c devices/tpms_abarth124.c devices/tpms_ave.c devices/tpms_bmw.c diff --git a/src/devices/thermor.c b/src/devices/thermor.c new file mode 100644 index 000000000..0a6cfc989 --- /dev/null +++ b/src/devices/thermor.c @@ -0,0 +1,223 @@ +/** @file + Thermor DG950 weather station. + + Copyright (C) 2024 Nicolas Gagné, Bruno OCTAU (ProfBoc75) + Copyright (C) 2024 Christian W. Zuckschwerdt + + 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" + +/** +Thermor DG950 weather station. + +The weather station is composed of: +- Display Receiver DG950R, FCC test reports are available at: https://fccid.io/S24DG950R +- Thermometer-Transmitter Sensor DG950, FCC test reports are available at: https://fccid.io/S24DG950 +- Wind Sensor (speed and direction) DG950 +- Rain Gauge Sensor (cumulative rainfall) DG950 + +The manual is available at: https://fccid.io/S24DG950R/Users-Manual/USERS-MANUAL-522434 +Review of the station: https://www.home-weather-stations-guide.com/thermor-weather-station.html + + +S.a #2879 open by Nicolas Gagné. + +RF raw Signal: 96 synchro pulses, 13 x [gap, start 1 bit 0, 8 bit] + + {213}0000000000000000000000000c3adfe6b1f0f92eff258f4fe1f0f8 + +Flex decoder: + + rtl_433 -X 'n=thermor,m=OOK_PWM,s=750,l=2128,y=1438,g=3000,r=8000,get=byte:@1:{8}:%x' + + {9}0c0, {9}758, {9}7f8, {9}358, {9}1f0, {9}1f0, {9}4b8, {9}7f8, {9}258, {9}1e8, {9}3f8, {9}0f8, {9}0f8 + +Samples here from Nicolas Gagné: + +https://github.com/NicolasGagne/rtl_433_tests/tree/a20b49805ed7ba74db016ec43e2a34ccda8231a9/tests/Thermor%20DG950 + +Data Layout: (normal mode) + + Byte position 0 1 2 3 4 5 6 7 8 9 10 11 12 + bit position 0 12345678 0 12345678 0 12345678 0 12345678 0 1234 5678 0 1234 5678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 + Data X IIIIIIII X TEMP_INT X Rain_mm X TEMP_CHK X WDIR FLAG X WDIR FLAG X AAAA_LSB X AAAA_MSB X BBBBBBBB X CCCC_CHK X TEMP_DEC X ???????? X Rain_mm+7 + +All bytes are reflected/reverse8 + +- II:{8} ID +- TEMP_INT:{8} temperature interger part, offset +195, °C +- TEMP_DEC:{8} temperature decimal part, offset +245, scale 10 +- RTmm:{4} rain rate in 0.1 mm +- RCHK:{4} rain rate in 0.1 mm offset +7 , used to check if same as RTmm +- TEMP_CHK:{8} Temp checksum +- WDIR:{4} wind direction, map table to be used +- FLAG:{4} if 1 = valid win dir, if 0 wind dir = unknown +- A_L, A_M, B, C_CHK :{8} values related to Wind Speed, C = A_L + A_M + B +- ?:{8} or {4} unknown values, fixed. +- X:{1} Bit start 0 is ignore + + +*/ + +static int thermor_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + if (bitbuffer->num_rows != 13) { + return DECODE_ABORT_EARLY; + } + + uint8_t b[13]; + + for (int row = 0; row < bitbuffer->num_rows; ++row) { + if (bitbuffer->bits_per_row[row] != 9) { + return DECODE_ABORT_EARLY; + } + // test is start bit = 0 + if ((bitbuffer->bb[row][0] & 0x80) != 0) { + return DECODE_ABORT_EARLY; + } + // extract only the 8 bit, ignore the start bit 0 + bitbuffer_extract_bytes(bitbuffer, row, 1, &b[row], 8); + } + // Test if Sync/pairing or normal signal + reflect_bytes(b,13); + decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, "Reflected"); + + int const wind_dir_degr[] = {157,45,135,67,180,22,112,90,225,337,247,315,202,0,270,292}; + + //sync pairing mode + if (b[0] == 0xff && b[1] == b[2] && b[1] == b[4] && b[1] == b[5] && b[1] == b[6] && b[1] == b[7] && b[1] == b[8] && b[1] == b[10] ) { + int new_id = ~b[1] & 0xff; + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Thermor-DG950", + "id", "", DATA_FORMAT, "%d", DATA_INT, new_id, + "pairing", "Pairing?", DATA_INT, 1, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + decoder_output_data(decoder, data); + return 1; + } + + else { + decoder_logf(decoder, 2, __func__, "Start decode ..."); + float wind_ratio = 0; + int have_rain = 0; + int have_wdir = 0; + int have_wspd = 0; + int rain_rate1 = 0; + int rain_rate2 = 0; + int wind_dir_d = 0; + int wind_coef = 0; + float wind_speed_kmh = 0; + + int id = ~b[0] & 0xff; + decoder_logf(decoder, 1, __func__, "ID %d", id); + + // Temp Checksum + int temp_chk = (b[1] + b[10]) & 0xff; + if ( (temp_chk + 1) != (b[3] & 0xff) ) { + decoder_logf(decoder, 2, __func__, "Temp Check Sum failed %d %d", temp_chk, (b[3] & 0xff)); + return DECODE_ABORT_EARLY; + } + + float temp_int = b[1] - 195; + float temp_dec = (b[10] - 245) * 0.1f; + float temp_C = temp_int + temp_dec; + decoder_logf(decoder, 2, __func__, "Temp %f", temp_C); + + // Test if valid rain + rain_rate1 = (~b[2] & 0xff); + rain_rate2 = (~b[12] & 0xff) -7; + // Rain check + if (rain_rate1 != rain_rate2) { + decoder_log(decoder, 2, __func__, "Rain Check failed"); + return DECODE_ABORT_EARLY; + } + have_rain = 1; + decoder_logf(decoder, 1, __func__, "Rain check passed ..."); + + // Test if valid wind direction + if ( b[4] != 0xff && b[5] != 0xff) { + if (b[4] != b[5]) { + decoder_log(decoder, 2, __func__, "Wind Direction Check failed"); + return DECODE_ABORT_EARLY; + } + wind_dir_d = wind_dir_degr[(b[4] & 0x0f)]; + have_wdir = 1; + } + + // Wind speed check sum + int wind_chk = (~b[6] + ~b[7] + ~b[8]) & 0xff; + if (wind_chk != (~b[9] & 0xff)) { + decoder_logf(decoder, 2, __func__, "Wind Check Sum failed %d %d", wind_chk, (~b[9] & 0xff)); + return DECODE_ABORT_EARLY; + } + decoder_logf(decoder, 2, __func__, "Wind Speed check passed ..."); + if (b[8] != 0xff ) { + uint16_t wind_speed_raw = (~b[6] & 0xff) | ((~b[7] & 0xff) << 8) ; + wind_coef = ~b[8] & 0xff; + if (wind_speed_raw < 256) { + wind_ratio = (wind_speed_raw * -0.0001746) + 0.155; + } + else { + wind_ratio = 0.11; + } + wind_speed_kmh = wind_ratio * (wind_speed_raw - wind_coef + 45); + if (wind_speed_kmh < 0 ) { + wind_speed_kmh = 0; + } + have_wspd = 1; + decoder_logf(decoder, 2, __func__, "Wind Speed calc passed ..."); + } + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Thermor-DG950", + "id", "", DATA_FORMAT, "%d", DATA_INT, id, + "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_C, + "rain_rate_mm_h", "Rain Rate", DATA_COND, have_rain, DATA_FORMAT, "%.1f mm/h", DATA_DOUBLE, rain_rate1 * 0.1f, + "wind_dir_deg", "Wind Direction", DATA_COND, have_wdir, DATA_INT, wind_dir_d, + "wind_avg_km_h", "Wind avg speed", DATA_COND, have_wspd, DATA_FORMAT, "%.1f km/h", DATA_DOUBLE, wind_speed_kmh, + //"wind_coef", "Wind Coef", DATA_COND, have_wspd, DATA_INT, wind_coef, + //"wind_ratio", "Wind Ratio", DATA_COND, have_wspd, DATA_DOUBLE, wind_ratio, + "pairing", "Pairing?", DATA_INT, 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", + "temperature_C", + "wind_avg_km_h", + "rain_rate_mm_h", + "wind_dir_deg", + "wind_ratio", + "wind_coef", + "pairing", + "mic", + NULL, +}; + +r_device const thermor = { + .name = "Thermor Weather Station DG950", + .modulation = OOK_PULSE_PWM, + .short_width = 680, + .long_width = 2100, + .sync_width = 1438, + .gap_limit = 3000, + .reset_limit = 8000, + .decode_fn = &thermor_decode, + .fields = output_fields, +}; From 389398f44c3dbb660ffaf4531ddcf0cc6f53e6d6 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Thu, 28 Mar 2024 18:43:46 +0100 Subject: [PATCH 02/11] Add Support for Mueller Hot Rod water meter --- README.md | 1 + include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/mueller_hotrod.c | 132 +++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 src/devices/mueller_hotrod.c diff --git a/README.md b/README.md index af9ff282d..d5f3c567f 100644 --- a/README.md +++ b/README.md @@ -339,6 +339,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [251] Fine Offset / Ecowitt WH55 water leak sensor [252] BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata [253] Watts WFHT-RF Thermostat + [254] Mueller Hot Rod water meter * Disabled by default, use -R n or a conf file to enable diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 80419954f..f8fe406ad 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -261,6 +261,7 @@ DECL(fineoffset_wh55) \ DECL(tpms_bmw) \ DECL(watts_thermostat) \ + DECL(mueller_hotrod) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40869dbca..9361b80ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -171,6 +171,7 @@ add_library(r_433 STATIC devices/mebus.c devices/megacode.c devices/missil_ml0757.c + devices/mueller_hotrod.c devices/neptune_r900.c devices/new_template.c devices/newkaku.c diff --git a/src/devices/mueller_hotrod.c b/src/devices/mueller_hotrod.c new file mode 100644 index 000000000..f94c28621 --- /dev/null +++ b/src/devices/mueller_hotrod.c @@ -0,0 +1,132 @@ +/** @file + Mueller Hot Rod water meter. + + Copyright (C) 2024 Christian W. Zuckschwerdt + 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" + +/** +Mueller Hot Rod water meter. + +S.a. #2719 Decoder desired for Mueller Systems Hot Rod transmitter (water meter), open by @howardtopher, related to Hod Rod v2 transmitter +S.a. #2752 Decoder for Mueller Hot Rod V1 Water Meter Transmitter, open by @dolai1, related to Hod Rod v1 transmitter + +Both version v1 and v2 protocols look same format. + +Flex decoder: + + rtl_433 -X 'n=hotrod,m=FSK_PCM,s=26,l=26,r=2500,preamble=feb100' + +Raw RF Signal: + + {136}ffffffffffd62002884cc2c092f1201f80 + {135}fff555555fd62002884cc2c092f1201f80 + {135}ffeaaaaabfac40051099858125e2403f00 + {134}000002aabfac40051099858125e54015c0 + {134}00000000000040051099858125e54015c0 + +The preamble is not stable because of the GFSK encoding not well handle by rtl_433. + +Data layout: + YY YY YY 0 1 2 3 4 5 6 7 8 9 10 11 ... + fe b1 00 II II II II GG GG GF FF CC ?? ?? ?? ... + +- YY: {24} Sync word 0xfeb100 +- II: {32} Device ID +- GG: {20} 5 niblles BCD water cumulative volume, gallons, scale 100. +- FF: {12} Flag, protocol version, battery_low ??? to be confirmed later. +- CC: {8} CRC-8/UTI, poly 0x07, init 0x00, xorout 0x55 +- ??: extra bit not used, related to GFSK/FSK encoding. + + +*/ + +static int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble_pattern[] = {0xfe, 0xb1, 0x00}; + + if (bitbuffer->num_rows != 1) { + decoder_log(decoder, 1, __func__, "Row check failed"); + return DECODE_ABORT_EARLY; + } + + // 3 byte for the sync word + 9 byte for data = 96 bits in total, too short if less + if (bitbuffer->bits_per_row[0] < 96) { + decoder_log(decoder, 1, __func__, "Len before preamble check failed"); + return DECODE_ABORT_LENGTH; + } + + // Find the preamble + unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); + if ((pos + 9 * 8) >= bitbuffer->bits_per_row[0]) { + decoder_log(decoder, 1, __func__, "Len after preamble check failed"); + return DECODE_ABORT_EARLY; + } + + uint8_t b[9]; + bitbuffer_extract_bytes(bitbuffer, 0, pos + 24, b, 72); // 9 x 8 bit + decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, "MSG"); + + // poly 0x07, init 0x00, xorout 0x55 + int crc_calc = crc8(b, 8, 0x07, 0x00) ^ 0x55; + if (crc_calc != b[8]) { + decoder_logf(decoder, 1, __func__, "CRC check failed : %0x %0x", b[8], crc_calc); + return 0; + } + + char id_str[16]; + snprintf(id_str, sizeof(id_str), "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); + + // 5 nibbles BCD (20 bit) x 100 = volume_gal + int volume = (((b[4] & 0xf0) >> 4) * 10000 + (b[4] & 0x0f) * 1000 + ((b[5] & 0xf0) >> 4) * 100 + (b[5] & 0x0f) * 10 + ((b[6] & 0xf0) >> 4)) * 100; + int flag1 = b[6] & 0x0f; + int flag2 = b[7] & 0xff; + + // volume could be from 6 or 7 nibbles, if 6 nibbles, scale 10, if 7 nibbles, scale 1. No more flag1 used to calculate the volume. To be confirmed. + // 6 nibbles BCD (24 bit) x 10 = volume_gal + //int volume = (((b[4] & 0xf0) >> 4)*100000+(b[4] & 0x0f)*10000+((b[5] & 0xf0) >> 4)*1000+(b[5] & 0x0f)*100+((b[6] & 0xf0) >> 4)*10+(b[6] & 0x0f)) * 10; + //int flag2 = b[7] & 0xff; + + // 7 nibbles BCD (28 bit) = volume_gal + //int volume = ((b[4] & 0xf0) >> 4)*1000000+(b[4] & 0x0f)*100000+((b[5] & 0xf0) >> 4)*10000+(b[5] & 0x0f)*1000+((b[6] & 0xf0) >> 4)*100+(b[6] & 0x0f)*10+((b[7] & 0xf0) >> 4); + //int flag2 = b[7] & 0x0f; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Mueller-Hot-Rod", + "id", "", DATA_STRING, id_str, + "volume_gal", "Volume", DATA_FORMAT, "%u gal", DATA_INT, volume, + "flag1", "Flag 1", DATA_FORMAT, "%x" , DATA_INT, flag1, + "flag2", "Flag 2", DATA_FORMAT, "%x" , DATA_INT, flag2, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + decoder_output_data(decoder, data); + return 1; +} + +static char const *const output_fields[] = { + "model", + "id", + "volume_gal", + "flag1", "flag2", + "mic", + NULL, +}; + +r_device const mueller_hotrod = { + .name = "Mueller Hot Rod water meter", + .modulation = FSK_PULSE_PCM, + .short_width = 26, + .long_width = 26, + .reset_limit = 2500, + .decode_fn = &mueller_hotrod_decode, + .fields = output_fields, +}; From b779a6fbe174d8170b958216bea3a1eaaed88ede Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Thu, 28 Mar 2024 18:50:35 +0100 Subject: [PATCH 03/11] minor remove whitespace --- src/devices/mueller_hotrod.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/mueller_hotrod.c b/src/devices/mueller_hotrod.c index f94c28621..d08ce893a 100644 --- a/src/devices/mueller_hotrod.c +++ b/src/devices/mueller_hotrod.c @@ -83,8 +83,8 @@ static int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer) char id_str[16]; snprintf(id_str, sizeof(id_str), "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); - - // 5 nibbles BCD (20 bit) x 100 = volume_gal + + // 5 nibbles BCD (20 bit) x 100 = volume_gal int volume = (((b[4] & 0xf0) >> 4) * 10000 + (b[4] & 0x0f) * 1000 + ((b[5] & 0xf0) >> 4) * 100 + (b[5] & 0x0f) * 10 + ((b[6] & 0xf0) >> 4)) * 100; int flag1 = b[6] & 0x0f; int flag2 = b[7] & 0xff; From e2a6058d7f08d0c5587cdcaf5fb9d455771f0331 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Thu, 28 Mar 2024 19:04:44 +0100 Subject: [PATCH 04/11] minor correct contributor names --- src/devices/mueller_hotrod.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/mueller_hotrod.c b/src/devices/mueller_hotrod.c index d08ce893a..baeee5143 100644 --- a/src/devices/mueller_hotrod.c +++ b/src/devices/mueller_hotrod.c @@ -15,8 +15,8 @@ /** Mueller Hot Rod water meter. -S.a. #2719 Decoder desired for Mueller Systems Hot Rod transmitter (water meter), open by @howardtopher, related to Hod Rod v2 transmitter -S.a. #2752 Decoder for Mueller Hot Rod V1 Water Meter Transmitter, open by @dolai1, related to Hod Rod v1 transmitter +S.a. #2719 Decoder desired for Mueller Systems Hot Rod transmitter (water meter), open by "howardtopher", related to Hod Rod v2 transmitter +S.a. #2752 Decoder for Mueller Hot Rod V1 Water Meter Transmitter, open by "dolai1", related to Hod Rod v1 transmitter Both version v1 and v2 protocols look same format. From 7a419d5bfb8052b21ffa190e053790353b44cda3 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Thu, 28 Mar 2024 19:12:48 +0100 Subject: [PATCH 05/11] minor correct model name --- src/devices/mueller_hotrod.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/mueller_hotrod.c b/src/devices/mueller_hotrod.c index baeee5143..851c9723c 100644 --- a/src/devices/mueller_hotrod.c +++ b/src/devices/mueller_hotrod.c @@ -100,7 +100,7 @@ static int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer) /* clang-format off */ data_t *data = data_make( - "model", "", DATA_STRING, "Mueller-Hot-Rod", + "model", "", DATA_STRING, "Mueller-HotRod", "id", "", DATA_STRING, id_str, "volume_gal", "Volume", DATA_FORMAT, "%u gal", DATA_INT, volume, "flag1", "Flag 1", DATA_FORMAT, "%x" , DATA_INT, flag1, From bcc9fb48582d407020e66f8ca3016367f468bfef Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Mon, 1 Apr 2024 11:22:19 +0200 Subject: [PATCH 06/11] Fix gallon values to 7 nibbles and resolve conflict --- README.md | 3 ++- include/rtl_433_devices.h | 1 + src/devices/mueller_hotrod.c | 39 +++++++++++++----------------------- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index d5f3c567f..9335b674e 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,8 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [251] Fine Offset / Ecowitt WH55 water leak sensor [252] BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata [253] Watts WFHT-RF Thermostat - [254] Mueller Hot Rod water meter + [254] Thermor DG950 Weather Station + [255] Mueller Hot Rod water meter * Disabled by default, use -R n or a conf file to enable diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index f8fe406ad..f01246de3 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -261,6 +261,7 @@ DECL(fineoffset_wh55) \ DECL(tpms_bmw) \ DECL(watts_thermostat) \ + DECL(thermor) \ DECL(mueller_hotrod) \ /* Add new decoders here. */ diff --git a/src/devices/mueller_hotrod.c b/src/devices/mueller_hotrod.c index 851c9723c..abdae660a 100644 --- a/src/devices/mueller_hotrod.c +++ b/src/devices/mueller_hotrod.c @@ -35,16 +35,16 @@ Raw RF Signal: The preamble is not stable because of the GFSK encoding not well handle by rtl_433. Data layout: - YY YY YY 0 1 2 3 4 5 6 7 8 9 10 11 ... - fe b1 00 II II II II GG GG GF FF CC ?? ?? ?? ... + PP PP PP YY YY YY 0 1 2 3 4 5 6 7 8 9 10 11 ... + aa aa aa fe b1 00 II II II II GG GG GG GF CC ?? ?? ?? ... +- PP: {xx} Preamble 0xaaaaa but not stable, see RF samples above. - YY: {24} Sync word 0xfeb100 - II: {32} Device ID -- GG: {20} 5 niblles BCD water cumulative volume, gallons, scale 100. -- FF: {12} Flag, protocol version, battery_low ??? to be confirmed later. +- GG: {28} 7 niblles BCD water cumulative volume, US liquid gallon +- FF: {4} Flag, protocol version, battery_low ??? to be confirmed later. - CC: {8} CRC-8/UTI, poly 0x07, init 0x00, xorout 0x55 -- ??: extra bit not used, related to GFSK/FSK encoding. - +- ??: extra trailing bit not used, related to GFSK/FSK encoding. */ @@ -53,20 +53,20 @@ static int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer) uint8_t const preamble_pattern[] = {0xfe, 0xb1, 0x00}; if (bitbuffer->num_rows != 1) { - decoder_log(decoder, 1, __func__, "Row check failed"); + decoder_log(decoder, 2, __func__, "Row check failed"); return DECODE_ABORT_EARLY; } // 3 byte for the sync word + 9 byte for data = 96 bits in total, too short if less if (bitbuffer->bits_per_row[0] < 96) { - decoder_log(decoder, 1, __func__, "Len before preamble check failed"); + decoder_log(decoder, 2, __func__, "Len before preamble check failed"); return DECODE_ABORT_LENGTH; } // Find the preamble unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); if ((pos + 9 * 8) >= bitbuffer->bits_per_row[0]) { - decoder_log(decoder, 1, __func__, "Len after preamble check failed"); + decoder_log(decoder, 2, __func__, "Len after preamble check failed"); return DECODE_ABORT_EARLY; } @@ -77,34 +77,23 @@ static int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer) // poly 0x07, init 0x00, xorout 0x55 int crc_calc = crc8(b, 8, 0x07, 0x00) ^ 0x55; if (crc_calc != b[8]) { - decoder_logf(decoder, 1, __func__, "CRC check failed : %0x %0x", b[8], crc_calc); + decoder_logf(decoder, 2, __func__, "CRC check failed : %0x %0x", b[8], crc_calc); return 0; } char id_str[16]; snprintf(id_str, sizeof(id_str), "%02x%02x%02x%02x", b[0], b[1], b[2], b[3]); - // 5 nibbles BCD (20 bit) x 100 = volume_gal - int volume = (((b[4] & 0xf0) >> 4) * 10000 + (b[4] & 0x0f) * 1000 + ((b[5] & 0xf0) >> 4) * 100 + (b[5] & 0x0f) * 10 + ((b[6] & 0xf0) >> 4)) * 100; - int flag1 = b[6] & 0x0f; - int flag2 = b[7] & 0xff; - - // volume could be from 6 or 7 nibbles, if 6 nibbles, scale 10, if 7 nibbles, scale 1. No more flag1 used to calculate the volume. To be confirmed. - // 6 nibbles BCD (24 bit) x 10 = volume_gal - //int volume = (((b[4] & 0xf0) >> 4)*100000+(b[4] & 0x0f)*10000+((b[5] & 0xf0) >> 4)*1000+(b[5] & 0x0f)*100+((b[6] & 0xf0) >> 4)*10+(b[6] & 0x0f)) * 10; - //int flag2 = b[7] & 0xff; - // 7 nibbles BCD (28 bit) = volume_gal - //int volume = ((b[4] & 0xf0) >> 4)*1000000+(b[4] & 0x0f)*100000+((b[5] & 0xf0) >> 4)*10000+(b[5] & 0x0f)*1000+((b[6] & 0xf0) >> 4)*100+(b[6] & 0x0f)*10+((b[7] & 0xf0) >> 4); - //int flag2 = b[7] & 0x0f; + int volume = ((b[4] & 0xf0) >> 4)*1000000+(b[4] & 0x0f)*100000+((b[5] & 0xf0) >> 4)*10000+(b[5] & 0x0f)*1000+((b[6] & 0xf0) >> 4)*100+(b[6] & 0x0f)*10+((b[7] & 0xf0) >> 4); + int flag = b[7] & 0x0f; /* clang-format off */ data_t *data = data_make( "model", "", DATA_STRING, "Mueller-HotRod", "id", "", DATA_STRING, id_str, "volume_gal", "Volume", DATA_FORMAT, "%u gal", DATA_INT, volume, - "flag1", "Flag 1", DATA_FORMAT, "%x" , DATA_INT, flag1, - "flag2", "Flag 2", DATA_FORMAT, "%x" , DATA_INT, flag2, + "flag", "Flag", DATA_FORMAT, "%x" , DATA_INT, flag, "mic", "Integrity", DATA_STRING, "CRC", NULL); /* clang-format on */ @@ -116,7 +105,7 @@ static char const *const output_fields[] = { "model", "id", "volume_gal", - "flag1", "flag2", + "flag", "mic", NULL, }; From cf3728bf5e2713885f9d49e1fffb925484a2e381 Mon Sep 17 00:00:00 2001 From: ProfBoc75 Date: Mon, 1 Apr 2024 11:24:14 +0200 Subject: [PATCH 07/11] resolve conflict --- src/CMakeLists.txt | 1 + src/devices/thermor.c | 223 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 src/devices/thermor.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9361b80ac..510101aa7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -231,6 +231,7 @@ add_library(r_433 STATIC devices/thermopro_tp12.c devices/thermopro_tx2.c devices/thermopro_tx2c.c + devices/thermor.c devices/tpms_abarth124.c devices/tpms_ave.c devices/tpms_bmw.c diff --git a/src/devices/thermor.c b/src/devices/thermor.c new file mode 100644 index 000000000..0a6cfc989 --- /dev/null +++ b/src/devices/thermor.c @@ -0,0 +1,223 @@ +/** @file + Thermor DG950 weather station. + + Copyright (C) 2024 Nicolas Gagné, Bruno OCTAU (ProfBoc75) + Copyright (C) 2024 Christian W. Zuckschwerdt + + 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" + +/** +Thermor DG950 weather station. + +The weather station is composed of: +- Display Receiver DG950R, FCC test reports are available at: https://fccid.io/S24DG950R +- Thermometer-Transmitter Sensor DG950, FCC test reports are available at: https://fccid.io/S24DG950 +- Wind Sensor (speed and direction) DG950 +- Rain Gauge Sensor (cumulative rainfall) DG950 + +The manual is available at: https://fccid.io/S24DG950R/Users-Manual/USERS-MANUAL-522434 +Review of the station: https://www.home-weather-stations-guide.com/thermor-weather-station.html + + +S.a #2879 open by Nicolas Gagné. + +RF raw Signal: 96 synchro pulses, 13 x [gap, start 1 bit 0, 8 bit] + + {213}0000000000000000000000000c3adfe6b1f0f92eff258f4fe1f0f8 + +Flex decoder: + + rtl_433 -X 'n=thermor,m=OOK_PWM,s=750,l=2128,y=1438,g=3000,r=8000,get=byte:@1:{8}:%x' + + {9}0c0, {9}758, {9}7f8, {9}358, {9}1f0, {9}1f0, {9}4b8, {9}7f8, {9}258, {9}1e8, {9}3f8, {9}0f8, {9}0f8 + +Samples here from Nicolas Gagné: + +https://github.com/NicolasGagne/rtl_433_tests/tree/a20b49805ed7ba74db016ec43e2a34ccda8231a9/tests/Thermor%20DG950 + +Data Layout: (normal mode) + + Byte position 0 1 2 3 4 5 6 7 8 9 10 11 12 + bit position 0 12345678 0 12345678 0 12345678 0 12345678 0 1234 5678 0 1234 5678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 + Data X IIIIIIII X TEMP_INT X Rain_mm X TEMP_CHK X WDIR FLAG X WDIR FLAG X AAAA_LSB X AAAA_MSB X BBBBBBBB X CCCC_CHK X TEMP_DEC X ???????? X Rain_mm+7 + +All bytes are reflected/reverse8 + +- II:{8} ID +- TEMP_INT:{8} temperature interger part, offset +195, °C +- TEMP_DEC:{8} temperature decimal part, offset +245, scale 10 +- RTmm:{4} rain rate in 0.1 mm +- RCHK:{4} rain rate in 0.1 mm offset +7 , used to check if same as RTmm +- TEMP_CHK:{8} Temp checksum +- WDIR:{4} wind direction, map table to be used +- FLAG:{4} if 1 = valid win dir, if 0 wind dir = unknown +- A_L, A_M, B, C_CHK :{8} values related to Wind Speed, C = A_L + A_M + B +- ?:{8} or {4} unknown values, fixed. +- X:{1} Bit start 0 is ignore + + +*/ + +static int thermor_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + if (bitbuffer->num_rows != 13) { + return DECODE_ABORT_EARLY; + } + + uint8_t b[13]; + + for (int row = 0; row < bitbuffer->num_rows; ++row) { + if (bitbuffer->bits_per_row[row] != 9) { + return DECODE_ABORT_EARLY; + } + // test is start bit = 0 + if ((bitbuffer->bb[row][0] & 0x80) != 0) { + return DECODE_ABORT_EARLY; + } + // extract only the 8 bit, ignore the start bit 0 + bitbuffer_extract_bytes(bitbuffer, row, 1, &b[row], 8); + } + // Test if Sync/pairing or normal signal + reflect_bytes(b,13); + decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, "Reflected"); + + int const wind_dir_degr[] = {157,45,135,67,180,22,112,90,225,337,247,315,202,0,270,292}; + + //sync pairing mode + if (b[0] == 0xff && b[1] == b[2] && b[1] == b[4] && b[1] == b[5] && b[1] == b[6] && b[1] == b[7] && b[1] == b[8] && b[1] == b[10] ) { + int new_id = ~b[1] & 0xff; + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Thermor-DG950", + "id", "", DATA_FORMAT, "%d", DATA_INT, new_id, + "pairing", "Pairing?", DATA_INT, 1, + "mic", "Integrity", DATA_STRING, "CHECKSUM", + NULL); + /* clang-format on */ + decoder_output_data(decoder, data); + return 1; + } + + else { + decoder_logf(decoder, 2, __func__, "Start decode ..."); + float wind_ratio = 0; + int have_rain = 0; + int have_wdir = 0; + int have_wspd = 0; + int rain_rate1 = 0; + int rain_rate2 = 0; + int wind_dir_d = 0; + int wind_coef = 0; + float wind_speed_kmh = 0; + + int id = ~b[0] & 0xff; + decoder_logf(decoder, 1, __func__, "ID %d", id); + + // Temp Checksum + int temp_chk = (b[1] + b[10]) & 0xff; + if ( (temp_chk + 1) != (b[3] & 0xff) ) { + decoder_logf(decoder, 2, __func__, "Temp Check Sum failed %d %d", temp_chk, (b[3] & 0xff)); + return DECODE_ABORT_EARLY; + } + + float temp_int = b[1] - 195; + float temp_dec = (b[10] - 245) * 0.1f; + float temp_C = temp_int + temp_dec; + decoder_logf(decoder, 2, __func__, "Temp %f", temp_C); + + // Test if valid rain + rain_rate1 = (~b[2] & 0xff); + rain_rate2 = (~b[12] & 0xff) -7; + // Rain check + if (rain_rate1 != rain_rate2) { + decoder_log(decoder, 2, __func__, "Rain Check failed"); + return DECODE_ABORT_EARLY; + } + have_rain = 1; + decoder_logf(decoder, 1, __func__, "Rain check passed ..."); + + // Test if valid wind direction + if ( b[4] != 0xff && b[5] != 0xff) { + if (b[4] != b[5]) { + decoder_log(decoder, 2, __func__, "Wind Direction Check failed"); + return DECODE_ABORT_EARLY; + } + wind_dir_d = wind_dir_degr[(b[4] & 0x0f)]; + have_wdir = 1; + } + + // Wind speed check sum + int wind_chk = (~b[6] + ~b[7] + ~b[8]) & 0xff; + if (wind_chk != (~b[9] & 0xff)) { + decoder_logf(decoder, 2, __func__, "Wind Check Sum failed %d %d", wind_chk, (~b[9] & 0xff)); + return DECODE_ABORT_EARLY; + } + decoder_logf(decoder, 2, __func__, "Wind Speed check passed ..."); + if (b[8] != 0xff ) { + uint16_t wind_speed_raw = (~b[6] & 0xff) | ((~b[7] & 0xff) << 8) ; + wind_coef = ~b[8] & 0xff; + if (wind_speed_raw < 256) { + wind_ratio = (wind_speed_raw * -0.0001746) + 0.155; + } + else { + wind_ratio = 0.11; + } + wind_speed_kmh = wind_ratio * (wind_speed_raw - wind_coef + 45); + if (wind_speed_kmh < 0 ) { + wind_speed_kmh = 0; + } + have_wspd = 1; + decoder_logf(decoder, 2, __func__, "Wind Speed calc passed ..."); + } + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Thermor-DG950", + "id", "", DATA_FORMAT, "%d", DATA_INT, id, + "temperature_C", "Temperature", DATA_FORMAT, "%.1f C", DATA_DOUBLE, temp_C, + "rain_rate_mm_h", "Rain Rate", DATA_COND, have_rain, DATA_FORMAT, "%.1f mm/h", DATA_DOUBLE, rain_rate1 * 0.1f, + "wind_dir_deg", "Wind Direction", DATA_COND, have_wdir, DATA_INT, wind_dir_d, + "wind_avg_km_h", "Wind avg speed", DATA_COND, have_wspd, DATA_FORMAT, "%.1f km/h", DATA_DOUBLE, wind_speed_kmh, + //"wind_coef", "Wind Coef", DATA_COND, have_wspd, DATA_INT, wind_coef, + //"wind_ratio", "Wind Ratio", DATA_COND, have_wspd, DATA_DOUBLE, wind_ratio, + "pairing", "Pairing?", DATA_INT, 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", + "temperature_C", + "wind_avg_km_h", + "rain_rate_mm_h", + "wind_dir_deg", + "wind_ratio", + "wind_coef", + "pairing", + "mic", + NULL, +}; + +r_device const thermor = { + .name = "Thermor Weather Station DG950", + .modulation = OOK_PULSE_PWM, + .short_width = 680, + .long_width = 2100, + .sync_width = 1438, + .gap_limit = 3000, + .reset_limit = 8000, + .decode_fn = &thermor_decode, + .fields = output_fields, +}; From 4749f0b16d8fc0b08ccccebe92a1cb6d54951215 Mon Sep 17 00:00:00 2001 From: Josh Pearce Date: Tue, 2 Apr 2024 08:13:09 -0400 Subject: [PATCH 08/11] Add support for ThermoPro TP28b (#2882) --- include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/thermopro_tp28b.c | 168 ++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 src/devices/thermopro_tp28b.c diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index f01246de3..27b267ed8 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -263,6 +263,7 @@ DECL(watts_thermostat) \ DECL(thermor) \ DECL(mueller_hotrod) \ + DECL(thermopro_tp28b) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 510101aa7..b82ce865f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -229,6 +229,7 @@ add_library(r_433 STATIC devices/tfa_twin_plus_30.3049.c devices/thermopro_tp11.c devices/thermopro_tp12.c + devices/thermopro_tp28b.c devices/thermopro_tx2.c devices/thermopro_tx2c.c devices/thermor.c diff --git a/src/devices/thermopro_tp28b.c b/src/devices/thermopro_tp28b.c new file mode 100644 index 000000000..8ec59f2e1 --- /dev/null +++ b/src/devices/thermopro_tp28b.c @@ -0,0 +1,168 @@ +/** @file + ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill. + + Copyright (C) 2024 Josh Pearce + + 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 thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer) +ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill. + + + +Example data: + + rtl_433 -f 915M -F json -X "n=tp28b,m=FSK_PCM,s=105,l=105,r=5500,preamble=d2aa2dd4" | jq --unbuffered -r '.codes[0]' + (spaces below added manually) + + {259}2802 0626 0000 2802 1107 0000 a290 6d70 a702 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 23C, over2: 71C, Temp2: 23C] + {259}2217 0626 0000 3102 1107 0000 a290 6d70 bf02 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 172, over2: 71C, Temp2: 23C] + {259}4421 1026 9009 3002 1012 4410 a298 6d70 5a03 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 214, over2: 121C, Temp2: 23C] + + + +Data layout: + + Bit bench format: A_TEMP:hhhh A_HI:hhhh A_LO:hhhh B_TEMP:hhhh B_HI:hhhh B_LO:hhhh FLAGS:hhhh ID:hhhh CHK:hhhh hhhhhhhhhhhh hhhh hhhhhhhhhhhhh + + [p1_temp] [p1_set_hi] [p1_set_lo] [p2_temp] [p2_set_hi] [p2_set_lo] [flags] [id] [cksum] 000000000000 aaaa 0000000000000 + + Temps are 16 bit Binary Coded Decimals (BCD), LLHH. They are always in Celsius. + Example: 2821, + 28 => 2.8 deg C + 21 => 210 deg C + 210 + 2.8 = 212.8 C (displayed rounded to 213) + + - p1_temp: probe 1 current temp. 16 bit BCD + - p1_set_hi: probe 1 high alarm temp. 16 bit BCD + - p1_set_lo: probe 1 low alarm temp. 16 bit BCD + - p2_temp: probe 2 current temp. 16 bit BCD + - p2_set_hi: probe 2 high alarm temp. 16 bit BCD + - p2_set_lo: probe 2 low alarm temp. 16 bit BCD + - flags: 16 bit status flags + - id: 16 bit identifier + - cksum: 16 bit checksum + +Below is some work on status/alarm flags, but I can't quite make sense of them all: + + 02d8 => F, p1: in-range, p2: in-range + 02f9 => F, p1: low, p2: in-range + 02dd => F, p1: in-range, p2: low + 02de => F, p1: in-range, p2: hi + 02fa => F, p1: hi, p2: in-range + 86f9 => F, p1: low, p2: low + 82f9 => F, p1: low, p2: low ack'd + a2f9 => C, p1: low, p2: low ack'd + a6f9 => C, p1: low, p2: low unack'd + + - flags & 0x2000 => Display in Celcius, otherwise Fahrenheit + - flags & 0x0400 => Alarm unacknowledged, otherwise acknowledged + - flags & 0x0020 => P1 in alarm, otherwise normal + - flags & 0x0004 => P2 in alarm, otherwise normal + - flags & 0x0001 => P2 in alarm low +*/ + +// Convert BCD encoded temp to float +static float bcd2float(uint8_t lo, uint8_t hi) +{ + return ((hi & 0xF0) >> 4) * 100.0f + (hi & 0x0F) * 10.0f + ((lo & 0xF0) >> 4) * 1.0f + (lo & 0x0F) * 0.1f; +} + +static int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4}; + + uint8_t b[18]; + + 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 < 240) { + decoder_logf(decoder, 1, __func__, "Packet too short: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + else if (msg_len > 451) { + decoder_logf(decoder, 1, __func__, "Packet too long: %d bits", msg_len); + return DECODE_ABORT_LENGTH; + } + else { + decoder_logf(decoder, 1, __func__, "packet length: %d", msg_len); + } + + 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; + } + + offset += sizeof(preamble_pattern) * 8; + bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 18 * 8); + + int sum = (add_bytes(b, 16) & 0xff) - b[16]; + if (sum) { + decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, "Checksum error"); + return DECODE_FAIL_MIC; + } + + decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0] - offset, ""); + + uint16_t id = b[15] | b[14] << 8; + uint16_t flags = b[13] | b[12] << 8; + float p1_temp = bcd2float(b[0], b[1]); + float p1_set_hi = bcd2float(b[2], b[3]); + float p1_set_lo = bcd2float(b[4], b[5]); + float p2_temp = bcd2float(b[6], b[7]); + float p2_set_hi = bcd2float(b[8], b[9]); + float p2_set_lo = bcd2float(b[10], b[11]); + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "ThermoPro-TP28b", + "id", "", DATA_FORMAT, "%04x", DATA_INT, id, + "temperature_1_C", "Temperature 1", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_temp, + "alarm_high_1_C", "Temperature 1 alarm high", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_set_hi, + "alarm_low_1_C", "Temperature 1 alarm low", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p1_set_lo, + "temperature_2_C", "Temperature 2", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_temp, + "alarm_high_2_C", "Temperature 2 alarm high", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_set_hi, + "alarm_low_2_C", "Temperature 2 alarm low", DATA_FORMAT, "%.1f C", DATA_DOUBLE, p2_set_lo, + "flags", "Status flags", DATA_FORMAT, "%04x", 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", + "temperature_1_C", + "alarm_high_1_C", + "alarm_low_1_C", + "temperature_2_C", + "alarm_high_2_C", + "alarm_low_2_C", + "flags", + "mic", + NULL, +}; + +r_device const thermopro_tp28b = { + .name = "ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill.", + .modulation = FSK_PULSE_PCM, + .short_width = 105, + .long_width = 105, + .reset_limit = 5500, + .decode_fn = &thermopro_tp28b_decode, + .fields = output_fields}; \ No newline at end of file From a50f235566a4de8b0099504f8dea5c8f145b01af Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Tue, 2 Apr 2024 14:25:43 +0200 Subject: [PATCH 09/11] minor: Cleanup ThermoPro-TP28b code style --- src/devices/thermopro_tp28b.c | 51 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/devices/thermopro_tp28b.c b/src/devices/thermopro_tp28b.c index 8ec59f2e1..7e9c8e091 100644 --- a/src/devices/thermopro_tp28b.c +++ b/src/devices/thermopro_tp28b.c @@ -14,8 +14,6 @@ /** @fn int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer) ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill. - - Example data: rtl_433 -f 915M -F json -X "n=tp28b,m=FSK_PCM,s=105,l=105,r=5500,preamble=d2aa2dd4" | jq --unbuffered -r '.codes[0]' @@ -25,30 +23,31 @@ Example data: {259}2217 0626 0000 3102 1107 0000 a290 6d70 bf02 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 172, over2: 71C, Temp2: 23C] {259}4421 1026 9009 3002 1012 4410 a298 6d70 5a03 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 214, over2: 121C, Temp2: 23C] +Data layout: + [p1_temp] [p1_set_hi] [p1_set_lo] [p2_temp] [p2_set_hi] [p2_set_lo] [flags] [id] [cksum] 000000000000 aaaa 0000000000000 -Data layout: +- p1_temp: probe 1 current temp. 16 bit BCD +- p1_set_hi: probe 1 high alarm temp. 16 bit BCD +- p1_set_lo: probe 1 low alarm temp. 16 bit BCD +- p2_temp: probe 2 current temp. 16 bit BCD +- p2_set_hi: probe 2 high alarm temp. 16 bit BCD +- p2_set_lo: probe 2 low alarm temp. 16 bit BCD +- flags: 16 bit status flags +- id: 16 bit identifier +- cksum: 16 bit checksum - Bit bench format: A_TEMP:hhhh A_HI:hhhh A_LO:hhhh B_TEMP:hhhh B_HI:hhhh B_LO:hhhh FLAGS:hhhh ID:hhhh CHK:hhhh hhhhhhhhhhhh hhhh hhhhhhhhhhhhh +Bit bench format: - [p1_temp] [p1_set_hi] [p1_set_lo] [p2_temp] [p2_set_hi] [p2_set_lo] [flags] [id] [cksum] 000000000000 aaaa 0000000000000 + A_TEMP:hhhh A_HI:hhhh A_LO:hhhh B_TEMP:hhhh B_HI:hhhh B_LO:hhhh FLAGS:hhhh ID:hhhh CHK:hhhh hhhhhhhhhhhh hhhh hhhhhhhhhhhhh + +Temps are little-endian 16 bit Binary Coded Decimals (BCD), LLHH. They are always in Celsius. - Temps are 16 bit Binary Coded Decimals (BCD), LLHH. They are always in Celsius. Example: 2821, 28 => 2.8 deg C 21 => 210 deg C 210 + 2.8 = 212.8 C (displayed rounded to 213) - - p1_temp: probe 1 current temp. 16 bit BCD - - p1_set_hi: probe 1 high alarm temp. 16 bit BCD - - p1_set_lo: probe 1 low alarm temp. 16 bit BCD - - p2_temp: probe 2 current temp. 16 bit BCD - - p2_set_hi: probe 2 high alarm temp. 16 bit BCD - - p2_set_lo: probe 2 low alarm temp. 16 bit BCD - - flags: 16 bit status flags - - id: 16 bit identifier - - cksum: 16 bit checksum - Below is some work on status/alarm flags, but I can't quite make sense of them all: 02d8 => F, p1: in-range, p2: in-range @@ -61,11 +60,11 @@ Below is some work on status/alarm flags, but I can't quite make sense of them a a2f9 => C, p1: low, p2: low ack'd a6f9 => C, p1: low, p2: low unack'd - - flags & 0x2000 => Display in Celcius, otherwise Fahrenheit - - flags & 0x0400 => Alarm unacknowledged, otherwise acknowledged - - flags & 0x0020 => P1 in alarm, otherwise normal - - flags & 0x0004 => P2 in alarm, otherwise normal - - flags & 0x0001 => P2 in alarm low +- flags & 0x2000 => Display in Celcius, otherwise Fahrenheit +- flags & 0x0400 => Alarm unacknowledged, otherwise acknowledged +- flags & 0x0020 => P1 in alarm, otherwise normal +- flags & 0x0004 => P2 in alarm, otherwise normal +- flags & 0x0001 => P2 in alarm low */ // Convert BCD encoded temp to float @@ -89,13 +88,10 @@ static int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer) decoder_logf(decoder, 1, __func__, "Packet too short: %d bits", msg_len); return DECODE_ABORT_LENGTH; } - else if (msg_len > 451) { + if (msg_len > 451) { decoder_logf(decoder, 1, __func__, "Packet too long: %d bits", msg_len); return DECODE_ABORT_LENGTH; } - else { - decoder_logf(decoder, 1, __func__, "packet length: %d", msg_len); - } int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8); @@ -114,7 +110,7 @@ static int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer) return DECODE_FAIL_MIC; } - decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0] - offset, ""); + decoder_log_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[0] - offset, ""); uint16_t id = b[15] | b[14] << 8; uint16_t flags = b[13] | b[12] << 8; @@ -165,4 +161,5 @@ r_device const thermopro_tp28b = { .long_width = 105, .reset_limit = 5500, .decode_fn = &thermopro_tp28b_decode, - .fields = output_fields}; \ No newline at end of file + .fields = output_fields, +}; From ac1e4a8c5a36fb90e3b06c0f01cef00bb3b2614d Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Tue, 2 Apr 2024 14:54:18 +0200 Subject: [PATCH 10/11] minor: Update readme --- README.md | 3 ++- conf/rtl_433.example.conf | 3 +++ src/devices/thermopro_tp28b.c | 2 +- src/devices/thermor.c | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9335b674e..17e6185e5 100644 --- a/README.md +++ b/README.md @@ -339,8 +339,9 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [251] Fine Offset / Ecowitt WH55 water leak sensor [252] BMW Gen5 TPMS, multi-brand HUF, Continental, Schrader/Sensata [253] Watts WFHT-RF Thermostat - [254] Thermor DG950 Weather Station + [254] Thermor DG950 weather station [255] Mueller Hot Rod water meter + [256] ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill * 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 9f45cc41f..1e6c1b122 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -480,6 +480,9 @@ convert si 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 + protocol 254 # Thermor DG950 weather station + protocol 255 # Mueller Hot Rod water meter + protocol 256 # ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill ## Flex devices (command line option "-X") diff --git a/src/devices/thermopro_tp28b.c b/src/devices/thermopro_tp28b.c index 7e9c8e091..78b2a60ec 100644 --- a/src/devices/thermopro_tp28b.c +++ b/src/devices/thermopro_tp28b.c @@ -155,7 +155,7 @@ static char const *const output_fields[] = { }; r_device const thermopro_tp28b = { - .name = "ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill.", + .name = "ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill", .modulation = FSK_PULSE_PCM, .short_width = 105, .long_width = 105, diff --git a/src/devices/thermor.c b/src/devices/thermor.c index 0a6cfc989..98dcbafd9 100644 --- a/src/devices/thermor.c +++ b/src/devices/thermor.c @@ -211,7 +211,7 @@ static char const *const output_fields[] = { }; r_device const thermor = { - .name = "Thermor Weather Station DG950", + .name = "Thermor DG950 weather station", .modulation = OOK_PULSE_PWM, .short_width = 680, .long_width = 2100, From 701757c00790c13118cf6135976da0a77dc4de05 Mon Sep 17 00:00:00 2001 From: Jeffrey Ruby Date: Wed, 3 Apr 2024 20:31:15 -0500 Subject: [PATCH 11/11] Update Neptune R900 for new meter type --- src/devices/neptune_r900.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/devices/neptune_r900.c b/src/devices/neptune_r900.c index e36d73348..4c496c8dd 100644 --- a/src/devices/neptune_r900.c +++ b/src/devices/neptune_r900.c @@ -50,14 +50,15 @@ After decoding the bitstream into 104 bits of payload, the layout appears to be: Data layout: - IIIIIIII IIIIIIII IIIIIIII IIIIIIII UUUUUUUU ???NNNBB CCCCCCCC CCCCCCCC CCCCCCCC UU?TTTLL EEEEEEEE EEEEEEEE EEEEEEEE + IIIIIIII IIIIIIII IIIIIIII IIIIIIII UUUUMMMM ???NNNBB CCCCCCCC CCCCCCCC CCCCCCCC UU?TTTLL EEEEEEEE EEEEEEEE EEEEEEEE - I: 32-bit little-endian id -- U: 8-bit Unknown1 +- U: 4-bit Unknown1 +- M: 4-bit Meter Type - N: 6-bit NoUse (3 bits) - B: 2-bit backflow flag - C: 24-bit Consumption Data, might be 1/10 gallon units -- U: 2-bit Unknown3 +- U: 2-bit Unknown3, perhaps added (first 2 bits) of consumption??? - T: 4-bit days of leak mapping (3 bits) - L: 2-bit leak flag type - E: 24-bit extra data???? @@ -144,8 +145,10 @@ static int neptune_r900_decode(r_device *decoder, bitbuffer_t *bitbuffer) // meter_id 32 bits uint32_t meter_id = ((uint32_t)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3]); - //Unkn1 8 bits - int unkn1 = b[4]; + //Unkn1 4 bits + int unkn1 = b[4] >> 4; + //MeterType 4 bits + int metertype = b[4]&0x0F; //Unkn2 3 bits int unkn2 = b[5] >> 5; //NoUse 3 bits @@ -163,8 +166,8 @@ static int neptune_r900_decode(r_device *decoder, bitbuffer_t *bitbuffer) // 1 = low // 2 = high int backflow = b[5]&0x03; - //Consumption 24 bits - int consumption = (b[6] << 16) | (b[7] << 8) | (b[8]); + //Consumption 26 bits ?? + int consumption = ((b[9] >> 6) << 24) | (b[6] << 16) | (b[7] << 8) | (b[8]); //Unkn3 2 bits + 1 bit ??? int unkn3 = b[9] >> 5; //Leak 3 bits @@ -191,6 +194,7 @@ static int neptune_r900_decode(r_device *decoder, bitbuffer_t *bitbuffer) "model", "", DATA_STRING, "Neptune-R900", "id", "", DATA_INT, meter_id, "unkn1", "", DATA_INT, unkn1, + "metertype", "", DATA_INT, metertype, "unkn2", "", DATA_INT, unkn2, "nouse", "", DATA_INT, nouse, "backflow", "", DATA_INT, backflow, @@ -218,6 +222,7 @@ static char const *const output_fields[] = { "model", "id", "unkn1", + "metertype", "unkn2", "nouse", "backflow",