From 023a52d59386d7c240c55ce1ba5f15454081f3bf Mon Sep 17 00:00:00 2001 From: Vladimir Sitnov Date: Wed, 11 Jan 2023 17:19:28 +0700 Subject: [PATCH 01/14] Fix HCS200 serial ID decoding (#2308) The 4 most significant bits of serial must be shifted by 24 (not 20) to get a correct 28-bit serial --- src/devices/hcs200.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/hcs200.c b/src/devices/hcs200.c index 616f687669..b60a6ec865 100644 --- a/src/devices/hcs200.c +++ b/src/devices/hcs200.c @@ -58,7 +58,7 @@ static int hcs200_callback(r_device *decoder, bitbuffer_t *bitbuffer) // The transmission is LSB first, big endian. uint32_t encrypted = ((unsigned)reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0])); - int serial = (reverse8(b[7] & 0xf0) << 20) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4])); + int serial = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4])); int btn = (b[7] & 0x0f); int btn_num = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2 int learn = (b[7] & 0x0f) == 0x0f; From 2cd5623c9ac65d49a542420ce91ac33ea645f32d Mon Sep 17 00:00:00 2001 From: obones Date: Wed, 11 Jan 2023 18:55:53 +0100 Subject: [PATCH 02/14] Fix light reading on Cotech-367959 when no sensor installed (#2305) --- src/devices/cotech_36_7959.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/devices/cotech_36_7959.c b/src/devices/cotech_36_7959.c index 4883ed0b31..184811d059 100644 --- a/src/devices/cotech_36_7959.c +++ b/src/devices/cotech_36_7959.c @@ -16,6 +16,7 @@ Also: SwitchDoc Labs Weather FT020T. Also: Sainlogic Weather Station WS019T Also: Sainlogic Weather Station FT0300 Also: Ragova WiFi Weather Station FT-0310 +Also: NicetyMeter Weather Station 0366 (without Lux or UV index) OOK modulated with Manchester encoding, halfbit-width 500 us. Message length is 112 bit, every second time it will transmit two identical messages, packet gap 5400 us. @@ -46,6 +47,7 @@ Message layout - X : 8 bit: CRC, poly 0x31, init 0xc0 */ +#include #include "decoder.h" static int cotech_36_7959_decode(r_device *decoder, bitbuffer_t *bitbuffer) @@ -106,6 +108,9 @@ static int cotech_36_7959_decode(r_device *decoder, bitbuffer_t *bitbuffer) float temp_c = (temp_raw - 400) * 0.1f; + // On models without a light sensor, the value read for UV index is out of bounds with its top bits set + bool light_is_valid = ((uv & 0xf0) == 0); + /* clang-format off */ data = data_make( "model", "", DATA_STRING, "Cotech-367959", @@ -118,8 +123,8 @@ static int cotech_36_7959_decode(r_device *decoder, bitbuffer_t *bitbuffer) "wind_dir_deg", "Wind direction", DATA_INT, wind_dir, "wind_avg_m_s", "Wind", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, wind * 0.1f, "wind_max_m_s", "Gust", DATA_FORMAT, "%.1f m/s", DATA_DOUBLE, gust * 0.1f, - "light_lux", "Light Intensity", DATA_FORMAT, "%u lux", DATA_INT, light_lux, - "uv", "UV Index", DATA_FORMAT, "%u", DATA_INT, uv, + "light_lux", "Light Intensity", DATA_COND, light_is_valid, DATA_FORMAT, "%u lux", DATA_INT, light_lux, + "uv", "UV Index", DATA_COND, light_is_valid, DATA_FORMAT, "%u", DATA_INT, uv, "mic", "Integrity", DATA_STRING, "CRC", NULL); /* clang-format on */ From 2070798d5e9bfc37e47e350475aaa466282281c5 Mon Sep 17 00:00:00 2001 From: Thomas Laroche Date: Fri, 13 Jan 2023 18:39:06 +0100 Subject: [PATCH 03/14] minor: Fix MSVC build (#2310) pthread was included on Windows platform even if it is not supported --- include/mongoose.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mongoose.h b/include/mongoose.h index b111ea34ea..2a6eb49271 100644 --- a/include/mongoose.h +++ b/include/mongoose.h @@ -4657,7 +4657,9 @@ size_t mg_fwrite(const void *ptr, size_t size, size_t count, FILE *f); #endif /* MG_ENABLE_FILESYSTEM */ #if MG_ENABLE_THREADS +#if CS_PLATFORM == CS_P_UNIX #include +#endif /* * Starts a new detached thread. * Arguments and semantics are the same as pthead's `pthread_create()`. From a95da78089052ae52fb09058b09671a4ae3e6652 Mon Sep 17 00:00:00 2001 From: Matthias Prinke <83612361+matthias-bs@users.noreply.github.com> Date: Fri, 13 Jan 2023 21:14:23 +0100 Subject: [PATCH 04/14] Fix Bresser-ProRainGauge rain digits (#2312) - Added additional digit for rain gauge --- src/devices/bresser_5in1.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/devices/bresser_5in1.c b/src/devices/bresser_5in1.c index 82260fb415..6692e3f949 100644 --- a/src/devices/bresser_5in1.c +++ b/src/devices/bresser_5in1.c @@ -42,7 +42,7 @@ Packet payload without preamble (203 bits): ef a1 ff ff 1f ff ef dc ff de df ff 7f 10 5e 00 00 e0 00 10 23 00 21 20 00 80 00 00 (low batt +ve temp) ed a1 ff ff 1f ff ef 8f ff d6 df ff 77 12 5e 00 00 e0 00 10 70 00 29 20 00 88 00 00 (low batt -ve temp -7.0C) ec 91 ff ff 1f fb ef e7 fe ad ed ff f7 13 6e 00 00 e0 04 10 18 01 52 12 00 08 00 00 (good batt -ve temp) - CC CC CC CC CC CC CC CC CC CC CC CC CC uu II SS GG DG WW W TT T HH RR R Bt + CC CC CC CC CC CC CC CC CC CC CC CC CC uu II SS GG DG WW W TT T HH RR RR Bt G-MSB ^ ^ W-MSB (strange but consistent order) - C = Check, inverted data of 13 byte further @@ -54,7 +54,7 @@ Packet payload without preamble (203 bits): - T = temperature in 1/10 °C, BCD coded, TTxT = 1203 => 31.2 °C - t = temperature sign, minus if unequal 0 - H = humidity in percent, BCD coded, HH = 23 => 23 % -- R = rain in mm, BCD coded, RRxR = 1203 => 31.2 mm +- R = rain in mm, BCD coded, RRRR = 1203 => 031.2 mm - B = Battery. 0=Ok, 8=Low. - S = sensor type, only low nibble used, 0x9 for Bresser Professional Rain Gauge */ @@ -120,7 +120,7 @@ static int bresser_5in1_decode(r_device *decoder, bitbuffer_t *bitbuffer) int wind_raw = (msg[18] & 0x0f) + ((msg[18] & 0xf0) >> 4) * 10 + (msg[19] & 0x0f) * 100; //fix merbanan/rtl_433#1315 float wind_avg = wind_raw * 0.1f; - int rain_raw = (msg[23] & 0x0f) + ((msg[23] & 0xf0) >> 4) * 10 + (msg[24] & 0x0f) * 100; + int rain_raw = (msg[23] & 0x0f) + ((msg[23] & 0xf0) >> 4) * 10 + (msg[24] & 0x0f) * 100 + ((msg[24] & 0xf0) >> 4) * 1000; float rain = rain_raw * 0.1f; int battery_low = (msg[25] & 0x80); From d287cfb785d01748cfe41286c63f7747b2afe623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ing=2E=20Jaroslav=20=C5=A0afka?= Date: Mon, 16 Jan 2023 09:49:40 +0100 Subject: [PATCH 05/14] Fix json keys for wmbus to be unique (#2316) * Key inst_volume_0 is now unique --- src/devices/m_bus.c | 74 +++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/devices/m_bus.c b/src/devices/m_bus.c index 6feae85330..b31325e608 100644 --- a/src/devices/m_bus.c +++ b/src/devices/m_bus.c @@ -280,18 +280,24 @@ static char *unit_names[][3] = { static double pow10_table[8] = { 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000 }; -static data_t *append_str(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn, char const *extra, char const *value) +static data_t *append_str(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn, + char const *key_extra, char const *pretty_extra, char const *value) { char key[100] = {0}; char pretty[100] = {0}; value_type &= 0x3; - snprintf(key, sizeof(key), "%s_%s_%d", value_types_tab[value_type][0], unit_names[unit_type][0], sn); - if (extra[0] == '\0') { + if (!key_extra || !*key_extra) { + snprintf(key, sizeof(key), "%s_%s_%d", value_types_tab[value_type][0], unit_names[unit_type][0], sn); + } else { + snprintf(key, sizeof(key), "%s_%s_%s_%d", value_types_tab[value_type][0], unit_names[unit_type][0], key_extra, sn); + } + + if (!pretty_extra || !*pretty_extra) { snprintf(pretty, sizeof(pretty), "%s %s[%d]", value_types_tab[value_type][1], unit_names[unit_type][1], sn); } else { - snprintf(pretty, sizeof(pretty), "%s %s %s", value_types_tab[value_type][1], unit_names[unit_type][1], extra); + snprintf(pretty, sizeof(pretty), "%s %s %s", value_types_tab[value_type][1], unit_names[unit_type][1], pretty_extra); } return data_append(data, @@ -299,7 +305,8 @@ static data_t *append_str(data_t *data, enum UnitType unit_type, uint8_t value_t } -static data_t *append_val(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn, char const *extra, int64_t val, int exp) +static data_t *append_val(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn, + char const *key_extra, char const *pretty_extra, int64_t val, int exp) { char const *prefix = ""; char buffer_val[256] = {0}; @@ -332,7 +339,7 @@ static data_t *append_val(data_t *data, enum UnitType unit_type, uint8_t value_t snprintf(buffer_val, sizeof(buffer_val), "%.03f %s%s", fvalue, prefix, unit_names[unit_type][2]); - return append_str(data, unit_type, value_type, sn, extra, buffer_val); + return append_str(data, unit_type, value_type, sn, key_extra, pretty_extra, buffer_val); } static size_t m_bus_tm_decode(const uint8_t *data, size_t data_size, char *output, size_t output_size) @@ -516,96 +523,97 @@ static int m_bus_decode_records(data_t *data, const uint8_t *b, uint8_t dif_codi case 0: if ((vif_uam&0xF8) == 0) { // E000 0nnn Energy 10nnn-3 Wh 0.001Wh to 10000Wh - data = append_val(data, kEnergy_Wh, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x7)); + data = append_val(data, kEnergy_Wh, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x7)); } else if ((vif_uam&0xF8) == 0x08) { // E000 1nnn Energy 10nnn J 0.001kJ to 10000kJ - data = append_val(data, kEnergy_J, dif_ff, dif_sn, "", val, vif_uam&0x7); + data = append_val(data, kEnergy_J, dif_ff, dif_sn, "", "", val, vif_uam&0x7); } else if ((vif_uam&0xF8) == 0x10) { // E001 0nnn Volume 10nnn-6 m3 0.001l to 10000l if (dif_sn == 0) { - data = append_val(data, kVolume, dif_ff, dif_sn, "", val, -6 + (vif_uam&0x7)); + data = append_val(data, kVolume, dif_ff, dif_sn, "", "", val, -6 + (vif_uam&0x7)); } else if (dif_sn >= 8 && dif_sn <= 19) { dif_sn -= 8; - data = append_val(data, kVolume, dif_ff, dif_sn, history_months[dif_sn][1], val, -6 + (vif_uam&0x7)); + data = append_val(data, kVolume, dif_ff, dif_sn, + history_months[dif_sn][0], history_months[dif_sn][1], val, -6 + (vif_uam&0x7)); } } else if ((vif_uam&0xF8) == 0x18) { // E001 1nnn Mass 10nnn-3 kg 0.001kg to 10000kg - data = append_val(data, kEnergy_J, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x7)); + data = append_val(data, kEnergy_J, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x7)); } else if ((vif_uam&0xFC) == 0x20) { /* E010 00nn On Time nn = 00 seconds nn = 01 minutes nn = 10 hours nn = 11 days */ switch (vif_uam&3) { - case 0: data = append_val(data, kOnTimeSec, dif_ff, dif_sn, "", val, 0); break; - case 1: data = append_val(data, kOnTimeMin, dif_ff, dif_sn, "", val, 0); break; - case 2: data = append_val(data, kOnTimeHours, dif_ff, dif_sn, "", val, 0); break; - case 3: data = append_val(data, kOnTimeDays, dif_ff, dif_sn, "", val, 0); break; + case 0: data = append_val(data, kOnTimeSec, dif_ff, dif_sn, "", "", val, 0); break; + case 1: data = append_val(data, kOnTimeMin, dif_ff, dif_sn, "", "", val, 0); break; + case 2: data = append_val(data, kOnTimeHours, dif_ff, dif_sn, "", "", val, 0); break; + case 3: data = append_val(data, kOnTimeDays, dif_ff, dif_sn, "", "", val, 0); break; default: break; } } else if ((vif_uam&0xFC) == 0x24) { // E010 01nn Operating Time coded like OnTime switch (vif_uam&3) { - case 0: data = append_val(data, kOperTimeSec, dif_ff, dif_sn, "", val, 0); break; - case 1: data = append_val(data, kOperTimeMin, dif_ff, dif_sn, "", val, 0); break; - case 2: data = append_val(data, kOperTimeHours, dif_ff, dif_sn, "", val, 0); break; - case 3: data = append_val(data, kOperTimeDays, dif_ff, dif_sn, "", val, 0); break; + case 0: data = append_val(data, kOperTimeSec, dif_ff, dif_sn, "", "", val, 0); break; + case 1: data = append_val(data, kOperTimeMin, dif_ff, dif_sn, "", "", val, 0); break; + case 2: data = append_val(data, kOperTimeHours, dif_ff, dif_sn, "", "", val, 0); break; + case 3: data = append_val(data, kOperTimeDays, dif_ff, dif_sn, "", "", val, 0); break; default: break; } } else if ((vif_uam&0xF8) == 0x28) { // E010 1nnn Power 10nnn-3 W 0.001W to 10000W - data = append_val(data, kPower_W, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x7)); + data = append_val(data, kPower_W, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x7)); } else if ((vif_uam&0xF8) == 0x30) { // E011 0nnn Power 10nnn J/h 0.001kJ/h to 10000kJ/h - data = append_val(data, kPower_Jh, dif_ff, dif_sn, "", val, vif_uam&0x7); + data = append_val(data, kPower_Jh, dif_ff, dif_sn, "", "", val, vif_uam&0x7); } else if ((vif_uam&0xF8) == 0x38) { // E011 1nnn Volume Flow 10nnn-6 m3/h 0.001l/h to 10000l/h - data = append_val(data, kVolumeFlow_h, dif_ff, dif_sn, "", val, -6 + (vif_uam&0x7)); + data = append_val(data, kVolumeFlow_h, dif_ff, dif_sn, "", "", val, -6 + (vif_uam&0x7)); } else if ((vif_uam&0xF8) == 0x40) { // E100 0nnn Volume Flow ext. 10nnn-7 m3/min 0.0001l/min to 1000l/min - data = append_val(data, kVolumeFlow_min, dif_ff, dif_sn, "", val, -7 + (vif_uam&0x7)); + data = append_val(data, kVolumeFlow_min, dif_ff, dif_sn, "", "", val, -7 + (vif_uam&0x7)); } else if ((vif_uam&0xF8) == 0x48) { // E100 1nnn Volume Flow ext. 10nnn-9 m³/s 0.001ml/s to 10000ml/s // in litres so exp -3 - data = append_val(data, kVolumeFlow_s, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x7)); + data = append_val(data, kVolumeFlow_s, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x7)); } else if ((vif_uam&0xF8) == 0x50) { // E101 0nnn Mass flow 10nnn-3 kg/h 0.001kg/h to 10000kg/h - data = append_val(data, kMassFlow, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x7)); + data = append_val(data, kMassFlow, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x7)); } else if ((vif_uam&0xFC) == 0x58) { // E101 10nn Flow Temperature 10nn-3 °C 0.001°C to 1°C - data = append_val(data, kTemperatureFlow, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x3)); + data = append_val(data, kTemperatureFlow, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x3)); } else if ((vif_uam&0xFC) == 0x5C) { // E101 11nn Return Temperature 10nn-3 °C 0.001°C to 1°C - data = append_val(data, kTemperatureReturn, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x3)); + data = append_val(data, kTemperatureReturn, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x3)); } else if ((vif_uam&0xFC) == 0x60) { // E110 00nn Temperature Difference 10nn-3 K 1mK to 1000mK - data = append_val(data, kTemperatureDiff, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x3)); + data = append_val(data, kTemperatureDiff, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x3)); } else if ((vif_uam&0xFC) == 0x64) { // E110 01nn External temperature 10 nn-3 ° C 0.001 ° C to 1 ° C - data = append_val(data, kTemperatureExtern, dif_ff, dif_sn, history_hours[dif_sn&0x3], val, -3 + (vif_uam&0x3)); + data = append_val(data, kTemperatureExtern, dif_ff, dif_sn, "", history_hours[dif_sn&0x3], val, -3 + (vif_uam&0x3)); } else if ((vif_uam&0xFC) == 0x68) { // E110 10nn Pressure 10nn-3 bar 1mbar to 1000mbar - data = append_val(data, kPressure, dif_ff, dif_sn, "", val, -3 + (vif_uam&0x3)); + data = append_val(data, kPressure, dif_ff, dif_sn, "", "", val, -3 + (vif_uam&0x3)); } else if ((vif_uam&0xFE) == 0x6C) { // E110 110n Time Point n = 0 date, n = 1 time & date char buff_time[256] = {0}; if (vif_uam&1) { if (m_bus_tm_decode(b, dif_coding, buff_time, sizeof(buff_time))) { - data = append_str(data, kTimeDate, dif_ff, dif_sn, "", buff_time); + data = append_str(data, kTimeDate, dif_ff, dif_sn, "", "", buff_time); } } else { if (m_bus_tm_decode(b, dif_coding, buff_time, sizeof(buff_time))) { - data = append_str(data, kDate, dif_ff, dif_sn, "", buff_time); + data = append_str(data, kDate, dif_ff, dif_sn, "", "", buff_time); } } } else if (vif_uam == 0x6E) { // E110 1110 Units for H.C.A. dimensionless - data = append_val(data, kHca, dif_ff, dif_sn, "", val, 0); + data = append_val(data, kHca, dif_ff, dif_sn, "", "", val, 0); } else if ((vif_uam&0xFC) == 0x70) { // E111 00nn Averaging Duration coded like OnTime } else if ((vif_uam&0xFC) == 0x74) { From 676a40426b0b9931cdf9991f60926182e8a06118 Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Wed, 18 Jan 2023 21:56:51 +0100 Subject: [PATCH 06/14] minor: Update docs --- README.md | 6 +++--- man/man1/rtl_433.1 | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 801a941543..b779b71c4d 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [-w | help] Save data stream to output file (a '-' dumps samples to stdout) [-W | help] Save data stream to output file, overwrite existing file = Data output options = - [-F kv | json | csv | mqtt | influx | syslog | trigger | null | help] Produce decoded output in given format. + [-F log | kv | json | csv | mqtt | influx | syslog | trigger | null | help] Produce decoded output in given format. Append output to file with : (e.g. -F csv:log.csv), defaults to stdout. Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514 [-M time[:] | protocol | level | noise[:] | stats | bits | help] Add various meta data to each output. @@ -392,8 +392,8 @@ E.g. -X "n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repea = Output format option = - [-F kv|json|csv|mqtt|influx|syslog|trigger|null] Produce decoded output in given format. - Without this option the default is KV output. Use "-F null" to remove the default. + [-F log|kv|json|csv|mqtt|influx|syslog|trigger|null] Produce decoded output in given format. + Without this option the default is LOG and KV output. Use "-F null" to remove the default. Append output to file with : (e.g. -F csv:log.csv), defaults to stdout. Specify MQTT server with e.g. -F mqtt://localhost:1883 Add MQTT options with e.g. -F "mqtt://host:1883,opt=arg" diff --git a/man/man1/rtl_433.1 b/man/man1/rtl_433.1 index 921b57ae9b..c8ced5affc 100644 --- a/man/man1/rtl_433.1 +++ b/man/man1/rtl_433.1 @@ -124,7 +124,7 @@ Save data stream to output file (a '\-' dumps samples to stdout) Save data stream to output file, overwrite existing file .SS "Data output options" .TP -[ \fB\-F\fI kv | json | csv | mqtt | influx | syslog | trigger | null | help\fP ] +[ \fB\-F\fI log | kv | json | csv | mqtt | influx | syslog | trigger | null | help\fP ] Produce decoded output in given format. Append output to file with : (e.g. \-F csv:log.csv), defaults to stdout. Specify host/port for syslog with e.g. \-F syslog:127.0.0.1:1514 @@ -321,10 +321,10 @@ countonly : suppress detailed row output E.g. \-X "n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3" .SS "Output format option" .TP -[ \fB\-F\fI kv|json|csv|mqtt|influx|syslog|trigger|null\fP ] +[ \fB\-F\fI log|kv|json|csv|mqtt|influx|syslog|trigger|null\fP ] Produce decoded output in given format. .RS -Without this option the default is KV output. Use "\-F null" to remove the default. +Without this option the default is LOG and KV output. Use "\-F null" to remove the default. .RE .RS Append output to file with : (e.g. \-F csv:log.csv), defaults to stdout. From acb3a298ed2f96c11de269a32f857761673098d3 Mon Sep 17 00:00:00 2001 From: rstephan Date: Sat, 21 Jan 2023 12:21:21 +0100 Subject: [PATCH 07/14] Change LaCrosse TX invalid humidity handling (#2335) Invalid sensor data (0x0ff) will be suppressed from the output. --- src/devices/lacrosse.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/devices/lacrosse.c b/src/devices/lacrosse.c index 00f24954c0..da9af34f8b 100644 --- a/src/devices/lacrosse.c +++ b/src/devices/lacrosse.c @@ -118,10 +118,11 @@ static int lacrossetx_decode(r_device *decoder, bitbuffer_t *bitbuffer) // TODO: check if message length is a valid value //uint8_t msg_len = msg_nybbles[1]; - uint8_t msg_type = msg_nybbles[2]; - uint8_t sensor_id = (msg_nybbles[3] << 3) + (msg_nybbles[4] >> 1); - float msg_value = msg_nybbles[5] * 10 + msg_nybbles[6] + msg_nybbles[7] * 0.1f; - int msg_value_int = msg_nybbles[8] * 10 + msg_nybbles[9]; + uint8_t msg_type = msg_nybbles[2]; + uint8_t sensor_id = (msg_nybbles[3] << 3) + (msg_nybbles[4] >> 1); + uint16_t msg_value_raw = (msg_nybbles[5] << 8) | (msg_nybbles[6] << 4) | msg_nybbles[7]; + float msg_value = msg_nybbles[5] * 10 + msg_nybbles[6] + msg_nybbles[7] * 0.1f; + int msg_value_int = msg_nybbles[8] * 10 + msg_nybbles[9]; // Check Repeated data values as another way of verifying // message integrity. @@ -152,7 +153,7 @@ static int lacrossetx_decode(r_device *decoder, bitbuffer_t *bitbuffer) data_t *data = data_make( "model", "", DATA_STRING, "LaCrosse-TX", "id", "", DATA_INT, sensor_id, - "humidity", "Humidity", DATA_FORMAT, "%.1f %%", DATA_DOUBLE, msg_value, + "humidity", "Humidity", DATA_COND, msg_value_raw != 0xff, DATA_FORMAT, "%.1f %%", DATA_DOUBLE, msg_value, "mic", "Integrity", DATA_STRING, "PARITY", NULL); /* clang-format on */ From 8d54e8a8d86706963447d3d90fed2b65595c402d Mon Sep 17 00:00:00 2001 From: UnscrewLater <74595797+UnscrewLater@users.noreply.github.com> Date: Sat, 21 Jan 2023 17:53:27 +0000 Subject: [PATCH 08/14] Add support for GEO minim+ energy monitor (#1970) Co-authored-by: Lawrence Rust Co-authored-by: Christian W. Zuckschwerdt --- README.md | 1 + conf/rtl_433.example.conf | 1 + include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/geo_minim.c | 354 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 src/devices/geo_minim.c diff --git a/README.md b/README.md index b779b71c4d..855d3a7e1a 100644 --- a/README.md +++ b/README.md @@ -306,6 +306,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [221] Fine Offset Electronics WN34 temperature sensor [222] Rubicson Pool Thermometer 48942 [223] Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) + [224] GEO minim+ energy monitor * 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 166115d237..0c947abd17 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -445,6 +445,7 @@ stop_after_successful_events false protocol 221 # Fine Offset Electronics WN34 temperature sensor protocol 222 # Rubicson Pool Thermometer 48942 protocol 223 # Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) + protocol 224 # GEO minim+ energy monitor ## Flex devices (command line option "-X") diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index 840a10f1b6..d09ad07b92 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -231,6 +231,7 @@ DECL(fineoffset_wn34) \ DECL(rubicson_pool_48942) \ DECL(badger_orion) \ + DECL(geo_minim) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84507bd952..5df74b9814 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -117,6 +117,7 @@ add_library(r_433 STATIC devices/generic_motion.c devices/generic_remote.c devices/generic_temperature_sensor.c + devices/geo_minim.c devices/govee.c devices/gt_tmbbq05.c devices/gt_wt_02.c diff --git a/src/devices/geo_minim.c b/src/devices/geo_minim.c new file mode 100644 index 0000000000..aa2a7487f3 --- /dev/null +++ b/src/devices/geo_minim.c @@ -0,0 +1,354 @@ +/** @file + GEO mimim+ energy monitor. + + Copyright (C) 2022 Lawrence Rust, lvr at softsystem dot co dot uk + + 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. +*/ + +/** @fn int minim_decode(r_device *decoder, bitbuffer_t *bitbuffer) +GEO mimim+ energy monitor. + +@sa geo_minim_ct_sensor_decode() +@sa geo_minim_display_decode() + +The GEO minim+ energy monitor comprises a sensor unit and a display unit. +https://assets.geotogether.com/sites/4/20170719152420/Minim-Data-sheet.pdf + +The sensor unit is supplied with a detachable current transformer that is +clipped around the live wire feeding the monitored device. The sensor unit +is powered by 3x AA batteries that provide for ~2 years of operation. It +transmits a short (5mS) data packet every ~3 seconds. + +Frequency 868.29 MHz, bit period 25 microseconds (40kbps), modulation FSK_PCM + +The display unit requires a 5V supply, provided by the supplied mains/USB +adapter. The display and sensor units are paired during initial power on +or as follows: + +1. On the display, hold down the <- and +> buttons together for 3 seconds. +2. At the next screen, hold down the middle button for 3 seconds until the + display shows “Pair?” +3. On the sensor, press and hold the pair button (next to the red light) + until the red LED light illuminates. +4. Release the pair button and the LED flashes as the transmitter pairs. +5. The display should now read “Paired CT" + +When paired the display listens for sensor packets and then transmits a +summary packet using the same protocol. + +The following Flex decoder will capture the raw data: + + rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb9' +*/ + +#include + +#include "decoder.h" + +/** +GEO minim+ current sensor. + +Packet layout: + +- 24 bit preamble of alternating 0s and 1s +- 2 sync bytes: 0x7b 0xb9 +- 4 byte header: 0x3f 0x06 0x29 0x05 +- 5 data bytes +- CRC16 + +The following Flex decoder will capture the raw sensor data: + + rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+ sensor,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb93f' + +Data format string: + + ID:24h VA:13d 3x UP:24d CRC:16h + + VA: Big endian power x10VA, bit14 = 5VA + UP: Big endian uptime x9 seconds +*/ +static int geo_minim_ct_sensor_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const buf[], unsigned len) +{ + (void)bitbuffer; + + if (len != 11) { + decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len, + "Incorrect length. Expected 11 got %u bytes", len); + return DECODE_ABORT_LENGTH; + } + + char id[9]; + snprintf(id, sizeof(id), "%02X%02X%02X%02X", buf[0], buf[1], buf[2], buf[3]); + + // Uptime in ~8 second intervals + unsigned uptime_raw = (buf[6] << 16) + (buf[7] << 8) + buf[8]; + unsigned uptime_s = 8 * uptime_raw; + + // Bytes 4 & 5 appear to be the instantaneous VA x10. + // When scaled by the 'Fine Tune' setting (power factor [0.88]) set on the + // display unit it matches the Watts value in display messages. + unsigned va = 10 * (buf[5] + ((buf[4] & 0x0f) << 8)); + if (buf[4] & 0x40) + va += 5; + + // TODO: what are the flag bits in buf[4] (0x30)? Battery OK, Fault? + unsigned flags4 = buf[4] & ~0x4f; + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "GEO-minimCT", + "id", "", DATA_STRING, id, + "power_VA", "Power", DATA_FORMAT, "%u VA", DATA_INT, va, + "flags4", "Flags", DATA_COND, flags4 != 0x30, DATA_FORMAT, "%#x", DATA_INT, flags4, + "uptime_s", "Uptime", DATA_STRING, uptime_s, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + + return 1; // Message successfully decoded +} + +/** +GEO minim+ display. + +Packet layout: + +- 24 bit preamble of alternating 0s and 1s +- 2 sync bytes: 0x7b 0xb9 +- 4 byte header: 0xea 0x01 0x35 0x2a +- 42 data bytes +- CRC16 + +The following Flex decoder will capture the raw display data: + + rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+ display,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb9ea' + +Data format string: + + ID:24h PWR:15d 1x 64x WH:11d 5x 64x 48x MIN:8d HRS:8d DAYS:16d 96x CRC:16h + + PWR: Instantaneous power, little endian + WH: Watt-hours in last 15 minutes, little endian + MIN,HRS,DAYs since 1/1/2007, little endian +*/ +static int geo_minim_display_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const buf[], unsigned len) +{ + (void)bitbuffer; + + uint8_t const zeroes[8] = { 0 }; + uint8_t const aaes[5] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }; + uint8_t const trailer[12] = { 0xaa, 0xff, 0xff, 0, 0, 0, 0, 0xaa, 0xff, 0xaa, 0xaa, 0 }; + + if (len != 48) { + decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len, + "Incorrect length. Expected 48, got %u bytes", len); + return DECODE_ABORT_LENGTH; + } + + // Report unexpected values + if (memcmp(zeroes, buf + 6, sizeof(zeroes))) { + decoder_logf_bitrow(decoder, 1, __func__, buf + 6, 8 * sizeof(zeroes), + "Nonzero @6"); + //return DECODE_FAIL_SANITY; + } + + if (memcmp(zeroes, buf + 16, sizeof(zeroes))) { + decoder_logf_bitrow(decoder, 1, __func__, buf + 16, 8 * sizeof(zeroes), + "Nonzero @16"); + //return DECODE_FAIL_SANITY; + } + + if (memcmp(aaes, buf + 24, sizeof(aaes))) { + decoder_logf_bitrow(decoder, 1, __func__, buf + 24, 8 * sizeof(aaes), + "Not 0xaa @24"); + //return DECODE_FAIL_SANITY; + } + + if (buf[29] != 0x00) { + decoder_logf(decoder, 1, __func__, + "Expected 0x00 but got %#x @29", buf[29]); + //return DECODE_FAIL_SANITY; + } + + if (memcmp(trailer, buf + 34, sizeof(trailer))) { + decoder_logf_bitrow(decoder, 1, __func__, buf + 34, 8 * sizeof(trailer), + "Bad trailer @34"); + //return DECODE_FAIL_SANITY; + } + + char id[9]; + snprintf(id, sizeof(id), "%02X%02X%02X%02X", buf[0], buf[1], buf[2], buf[3]); + + // Instantaneous power: 300W => 60: 1 = 5W + unsigned watts = 5 * (buf[4] + ((buf[5] & 0x7f) << 8)); + // TODO: what is bit7? + unsigned flags5 = buf[5] & ~0x7f; + + // Energy: 480W => 8/min: 1 = 0.06kWm = 0.001kWh + unsigned wh = buf[14] + ((buf[15] & 0x7) << 8); + // TODO: what are bits 3..7 ? 0x40 normally, Battery OK, Fault? + unsigned flags15 = buf[15] & ~0x7; + + struct tm t = {0}; + // Date/time @30..33 + t.tm_sec = 0; + t.tm_min = buf[33] & 0x3f; + t.tm_hour = buf[32] & 0x1f; + // Day 0 = 1/1/2007 + t.tm_mday = 1 + buf[30] + (buf[31] << 8); + t.tm_mon = 1 - 1; + t.tm_year = 2007 - 1900; + t.tm_isdst = -1; + // Normalise the date + mktime(&t); + char now[64]; + snprintf(now, sizeof(now), "%04d-%02d-%02d %02d:%02d", + 1900 + t.tm_year, 1 + t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min); + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "GEO-minimDP", + "id", "", DATA_STRING, id, + "power_W", "Power", DATA_FORMAT, "%u W", DATA_INT, watts, + "energy_kWh", "Energy", DATA_FORMAT, "%.3f kWh", DATA_DOUBLE, wh * 0.001, + "clock", "Clock", DATA_STRING, now, + "flags5", "Flags5", DATA_COND, flags5 != 0, DATA_FORMAT, "%#x", DATA_INT, flags5, + "flags15", "Flags15", DATA_COND, flags15 != 0x40, DATA_FORMAT, "%#x", DATA_INT, flags15, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + + return 1; // Message successfully decoded +} + +// packet type magic numbers +#define MTYPE_DISPLAY 0xea +#define MTYPE_CT 0x3f + +static int minim_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const pre[] = { 0x55, 0x55 }; + const unsigned prelen = 8 * sizeof(pre); + uint8_t const syn[] = { 0x7b, 0xb9 }; + const unsigned synlen = 8 * sizeof(syn); + + if (bitbuffer->num_rows != 1) + return DECODE_ABORT_LENGTH; + + unsigned row = 0; // we expect only one row + + if (bitbuffer->bits_per_row[row] <= prelen) + return DECODE_ABORT_LENGTH; + + unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, pre, prelen) + prelen; + if (bitpos >= bitbuffer->bits_per_row[row]) + return DECODE_ABORT_EARLY; + + if (bitbuffer->bits_per_row[row] <= bitpos + synlen) + return DECODE_ABORT_LENGTH; + + bitpos = bitbuffer_search(bitbuffer, row, bitpos, syn, synlen) + synlen; + if (bitpos >= bitbuffer->bits_per_row[row]) { + if (decoder->verbose >= 1) + decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, "No sync"); + return DECODE_ABORT_EARLY; + } + + unsigned bits = bitbuffer->bits_per_row[row]; + + // Extract frame header + unsigned const hdr_len = 4; + unsigned const hdr_bits = hdr_len * 8; + if (bitpos + hdr_bits >= bits) + return DECODE_ABORT_LENGTH; + + bits -= bitpos; + uint8_t buf[128]; + bitbuffer_extract_bytes(bitbuffer, row, bitpos, buf, hdr_bits); + + // Determine frame type. Assume: + // buf[0] = message type + // buf[1], buf[2] = session ID from pairing + // buf[3] = data byte length + // e.g. ea 01 35 2a : Display + // e.g. 3f 06 29 05 : CT sensor + int mtype = buf[0]; + if (mtype != MTYPE_DISPLAY && mtype != MTYPE_CT) { + decoder_logf(decoder, 1, __func__, + "Unknown header %02x%02x%02x%02x", + buf[0], buf[1], buf[2], buf[3]); + return DECODE_ABORT_EARLY; + } + + unsigned bytes = bits / 8; + unsigned maxlen = sizeof(buf); + if (bytes > maxlen) { + decoder_logf(decoder, 1, __func__, + "Too big: %u > %u max bytes", bits / 8, maxlen); + //return DECODE_ABORT_LENGTH; + bytes = maxlen; + } + + // Check offset to crc16 using data_len @ header[3] + unsigned crc_len = hdr_len + buf[3]; + if (crc_len + 2 > bytes) { + decoder_logf(decoder, 1, __func__, + "Truncated - got %u of %u bytes", bytes, crc_len + 2); + return DECODE_FAIL_SANITY; + } + + // Extract byte-aligned data + bitbuffer_extract_bytes(bitbuffer, row, bitpos + hdr_bits, buf + hdr_len, (bytes - hdr_len) * 8); + + // Message Integrity Check + unsigned crc = crc16(buf, crc_len, 0x8005, 0); + unsigned crc_rcvd = (buf[crc_len] << 8) | buf[crc_len + 1]; + if (crc != crc_rcvd) { + decoder_logf_bitrow(decoder, 1, __func__, buf, (crc_len + 2) * 8, + "Bad CRC. Expected %04X got %04X", crc, crc_rcvd); + return DECODE_FAIL_MIC; + } + + if (mtype == MTYPE_DISPLAY) { + return geo_minim_display_decode(decoder, bitbuffer, buf, bytes); + } + if (mtype == MTYPE_CT) { + return geo_minim_ct_sensor_decode(decoder, bitbuffer, buf, bytes); + } + + return DECODE_FAIL_SANITY; +} + +// List of fields to appear in the `-F csv` output. +static char *output_fields[] = { + "model", + "id", + "power_VA", + "flags4", + "uptime_s", + "power_W", + "energy_kWh", + "clock", + "flags5", + "flags15", + "mic", + NULL, +}; + +r_device geo_minim = { + .name = "GEO minim+ energy monitor", + .modulation = FSK_PULSE_PCM, + .short_width = 24, + .long_width = 24, + .reset_limit = 3000, + .decode_fn = &minim_decode, + .fields = output_fields, +}; From 4f533e0e325927a4b9109f9a919c82bb8942c22e Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Sat, 21 Jan 2023 19:12:57 +0100 Subject: [PATCH 09/14] minor: Clean up Geo-minim --- src/devices/geo_minim.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/devices/geo_minim.c b/src/devices/geo_minim.c index aa2a7487f3..c3838056b3 100644 --- a/src/devices/geo_minim.c +++ b/src/devices/geo_minim.c @@ -104,7 +104,7 @@ static int geo_minim_ct_sensor_decode(r_device *decoder, bitbuffer_t *bitbuffer, "id", "", DATA_STRING, id, "power_VA", "Power", DATA_FORMAT, "%u VA", DATA_INT, va, "flags4", "Flags", DATA_COND, flags4 != 0x30, DATA_FORMAT, "%#x", DATA_INT, flags4, - "uptime_s", "Uptime", DATA_STRING, uptime_s, + "uptime_s", "Uptime", DATA_INT, uptime_s, "mic", "Integrity", DATA_STRING, "CRC", NULL); /* clang-format on */ @@ -235,30 +235,24 @@ static int geo_minim_display_decode(r_device *decoder, bitbuffer_t *bitbuffer, u static int minim_decode(r_device *decoder, bitbuffer_t *bitbuffer) { - uint8_t const pre[] = { 0x55, 0x55 }; - const unsigned prelen = 8 * sizeof(pre); - uint8_t const syn[] = { 0x7b, 0xb9 }; - const unsigned synlen = 8 * sizeof(syn); + // preamble and sync can be aaaa7bb9 or 55557bb9 + uint8_t const preamble1[] = { 0xaa, 0xaa, 0x7b, 0xb9 }; + uint8_t const preamble2[] = { 0x55, 0x55, 0x7b, 0xb9 }; + const unsigned preamble_len = 8 * sizeof(preamble1); if (bitbuffer->num_rows != 1) return DECODE_ABORT_LENGTH; unsigned row = 0; // we expect only one row - if (bitbuffer->bits_per_row[row] <= prelen) - return DECODE_ABORT_LENGTH; - - unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, pre, prelen) + prelen; - if (bitpos >= bitbuffer->bits_per_row[row]) - return DECODE_ABORT_EARLY; - - if (bitbuffer->bits_per_row[row] <= bitpos + synlen) - return DECODE_ABORT_LENGTH; - - bitpos = bitbuffer_search(bitbuffer, row, bitpos, syn, synlen) + synlen; + // Search preamble+sync, try alternative + unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, preamble1, preamble_len) + preamble_len; + if (bitpos >= bitbuffer->bits_per_row[row]) { + bitpos = bitbuffer_search(bitbuffer, row, 0, preamble2, preamble_len) + preamble_len; + } if (bitpos >= bitbuffer->bits_per_row[row]) { - if (decoder->verbose >= 1) - decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, "No sync"); + if (decoder->verbose >= 2) + decoder_logf_bitbuffer(decoder, 3, __func__, bitbuffer, "Sync not found"); return DECODE_ABORT_EARLY; } From 8c5e38f3f5ced0730289d3880d6fd23868d0f0dc Mon Sep 17 00:00:00 2001 From: Topper69 <39837040+Topper69@users.noreply.github.com> Date: Sat, 21 Jan 2023 19:54:04 +0100 Subject: [PATCH 10/14] Add support for TyreGuard 400 TPMS (#1976) Co-authored-by: Christian W. Zuckschwerdt --- README.md | 1 + conf/rtl_433.example.conf | 1 + conf/tyreguard400.conf | 52 ++++++++ include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/tpms_tyreguard400.c | 203 ++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+) create mode 100644 conf/tyreguard400.conf create mode 100644 src/devices/tpms_tyreguard400.c diff --git a/README.md b/README.md index 855d3a7e1a..2eac31611b 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [222] Rubicson Pool Thermometer 48942 [223] Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) [224] GEO minim+ energy monitor + [225] TyreGuard 400 TPMS * 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 0c947abd17..20b11387d0 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -446,6 +446,7 @@ stop_after_successful_events false protocol 222 # Rubicson Pool Thermometer 48942 protocol 223 # Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) protocol 224 # GEO minim+ energy monitor + protocol 225 # TyreGuard 400 TPMS ## Flex devices (command line option "-X") diff --git a/conf/tyreguard400.conf b/conf/tyreguard400.conf new file mode 100644 index 0000000000..3ad8f95324 --- /dev/null +++ b/conf/tyreguard400.conf @@ -0,0 +1,52 @@ +# TYREGUARD400 from DAVIES CRAIG +# +# https://daviescraig.com.au/product/tyreguard-400-tpms-4-sensors-kit-1015 +# +# - Type : TPMS +# - Freq : 434.1 MHz +# - Modulation : ASK -> OOK_MC_ZEROBIT (Manchester Code with fixed leading zero bit) +# - Sambol duration : 100us (same for l or 0) +# - Length : 22 bytes long +# +# Packet layout: +# +# bytes : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 +# coded : S/P S/P S/P S/P S/P S/P S/P ID ID ID ID ID ID ID Pr Pr Temp Temp Flg Flg CRC CRC +# +# - S/P : preamble/sync "0xfd5fd5f" << always fixed +# - ID : 6 bytes long start with 0x6b????? ex 0x6b20d21 +# - Pr : Last 2 bytes of pressure in psi ex : 0xe8 means XX232 psi (for XX see flags bytes) +# - Temp : Temperature in °C offset by +40 ex : 0x2f means (47-40)=+7°C +# - Flg : Flags bytes => should be read in binary format : +# - Bit 73 : Unknown ; maybe the 20th MSB pressure bit? The sensor is not capable to reach this so high pressure +# - Bit 74 : add 1024 psi (19th MSB pressure bit) +# - Bit 75 : add 512 psi (18th MSB pressure bit) +# - Bit 76 : add 256 psi (17th MSB pressure bit) +# - Bit 77 : Acknoldge pressure leaking 1=Ack 0=No_ack (nothing to report) +# - Bit 78 : Unknown +# - Bit 79 : Leaking pressure detected 1=Leak 0=No leak (nothing to report) +# - Bit 80 : Leaking pressure detected 1=Leak 0=No leak (nothing to report) +# - CRC : CRC poly 0x31 start value 0xdd final 0x00 from 1st bit 80th bits +# +# To peer a new sensor to the unit, bit 79 and 80 has to be both to 1. +# +# NOTE: In the datasheet, it is said that the sensor can report low batterie. During my tests/reseach i'm not able to see this behavior. I have fuzzed all bits nothing was reported to the reader. +# + +decoder { + name = TPMS-TYREGUARD400, + modulation = OOK_MC_ZEROBIT, + short = 100, + long = 100, + gap = 0, + reset = 500, + preamble = fd5fd5f, + get = id:@0:{28}, + get = pression:@57:{8}, + get = temp:@65:{8}, + get = flags:@73:{8}, + get = add_psi:@74:{3}:[0:no 1:256 2:512 3:768 4:1024 5:1280 6:1536 7:1792], + get = AckLeaking:@77:{1}:[1: yes 0:no], + get = Leaking_detected:@79:{2}:[0:no 1:yes 2:yes], + get = CRC:@81:{8}, +} diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index d09ad07b92..ad75f3d4e4 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -232,6 +232,7 @@ DECL(rubicson_pool_48942) \ DECL(badger_orion) \ DECL(geo_minim) \ + DECL(tpms_tyreguard400) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5df74b9814..c219d7f193 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -227,6 +227,7 @@ add_library(r_433 STATIC devices/tpms_renault_0435r.c devices/tpms_toyota.c devices/tpms_truck.c + devices/tpms_tyreguard400.c devices/ts_ft002.c devices/ttx201.c devices/vaillant_vrt340f.c diff --git a/src/devices/tpms_tyreguard400.c b/src/devices/tpms_tyreguard400.c new file mode 100644 index 0000000000..faa3271269 --- /dev/null +++ b/src/devices/tpms_tyreguard400.c @@ -0,0 +1,203 @@ +/** @file + TPMS TyreGuard 400 from Davies Craig. + + Copyright (C) 2022 R ALVERGNAT + + 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. +*/ +/** +TPMS TyreGuard 400 from Davies Craig. + +- Type: TPMS +- Freq: 434.1 MHz +- Modulation: ASK -> OOK_MC_ZEROBIT (Manchester Code with fixed leading zero bit) +- Symbol duration: 100us (same for l or 0) +- Length: 22 bytes long + +Packet layout: + + bytes : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + coded : S/P S/P S/P S/P S/P S/P S/P ID ID ID ID ID ID ID Pr Pr Temp Temp Flg Flg CRC CRC + +- S/P : preamble/sync "0xfd5fd5f" << always fixed +- ID : 6 bytes long start with 0x6b????? ex 0x6b20d21 +- Pr : Last 2 bytes of pressure in psi ex : 0xe8 means XX232 psi (for XX see flags bytes) +- Temp : Temperature in °C offset by +40 ex : 0x2f means (47-40)=+7°C +- Flg : Flags bytes => should be read in binary format : + - Bit 73 : Unknown ; maybe the 20th MSB pressure bit? The sensor is not capable to reach this so high pressure + - Bit 74 : add 1024 psi (19th MSB pressure bit) + - Bit 75 : add 512 psi (18th MSB pressure bit) + - Bit 76 : add 256 psi (17th MSB pressure bit) + - Bit 77 : Acknoldge pressure leaking 1=Ack 0=No_ack (nothing to report) + - Bit 78 : Unknown + - Bit 79 : Leaking pressure detected 1=Leak 0=No leak (nothing to report) + - Bit 80 : Leaking pressure detected 1=Leak 0=No leak (nothing to report) +- CRC : CRC poly 0x31 start value 0xdd final 0x00 from 1st bit 80th bits + +To peer a new sensor to the unit, bit 79 and 80 has to be both to 1. + +NOTE: In the datasheet, it is said that the sensor can report low batterie. During my tests/reseach i'm not able to see this behavior. I have fuzzed all bits nothing was reported to the reader. + +Flex decoder: + + -X "n=TPMS,m=OOK_MC_ZEROBIT,s=100,l=100,r=500,preamble=fd5fd5f" + + decoder { + name = TPMS-TYREGUARD400, + modulation = OOK_MC_ZEROBIT, + short = 100, + long = 100, + gap = 0, + reset = 500, + preamble = fd5fd5f, + get = id:@0:{28}, + get = pression:@57:{8}, + get = temp:@65:{8}, + get = flags:@73:{8}, + get = add_psi:@74:{3}:[0:no 1:256 2:512 3:768 4:1024 5:1280 6:1536 7:1792], + get = AckLeaking:@77:{1}:[1: yes 0:no], + get = Leaking_detected:@79:{2}:[0:no 1:yes 2:yes], + get = CRC:@81:{8}, + } + +*/ + +#include "decoder.h" + +#define TPMS_TYREGUARD400_MESSAGE_BITLEN 88 + +static int tpms_tyreguard400_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos) +{ + uint8_t b[(TPMS_TYREGUARD400_MESSAGE_BITLEN + 7) / 8]; + + // Extract the message + bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, TPMS_TYREGUARD400_MESSAGE_BITLEN); + + //CRC poly 0x31 start value 0xdd final 0x00 from 1st bit to 80bits + if (crc8(b, 11, 0x31, 0xdd) != 0) { + decoder_log_bitrow(decoder, 2, __func__, b, TPMS_TYREGUARD400_MESSAGE_BITLEN, "CRC error"); + return DECODE_FAIL_MIC; + } + + uint8_t flags = b[9]; + //int bat_low = flags & 0x1; // TBC ?!? + int peering_request = flags & 0x3; // bytes = 0b00000011 + int ack_leaking = flags & 0x8; // byte = 0b00001000 :: NOTA ack_leaking = 1 means ack ;; ack_leaking=0 nothing to do + + // test if bits 1st or 2nd is set to 1 of 0b000000XX + int leaking = flags & 0x3; + + //int add256 = (flags & 0x10) >> 4; // bytes = 0b000X0000 + //int add512 = (flags & 0x20) >> 5; // bytes = 0b00X00000 + //int add1024 = (flags & 0x40) >> 6; // bytes = 0b0X000000 + + char id_str[8]; + sprintf(id_str, "%07x", (uint32_t)(((b[3] & 0xf)<<24)) | (b[4]<<16) | (b[5]<<8) | b[6]); // 28 bits ID + char flags_str[3]; + sprintf(flags_str, "%02x", flags); + + //id = (b[4] << 8) + int pressure_kpa = b[7] | ((flags & 0x70) << 4); + int temp_c = b[8] - 40; + + /* clang-format off */ + data_t *data = data_make( + "model", "Model", DATA_STRING, "TyreGuard400", + "type", "Type", DATA_STRING, "TPMS", + "id", "ID", DATA_STRING, id_str, +// "flags", "Flags", DATA_STRING, flags_str, + "pressure_kPa", "Pressure", DATA_FORMAT, "%.1f kPa", DATA_DOUBLE, (double)pressure_kpa, + "temperature_C", "Temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, (double)temp_c, + "peering_request", "Peering req", DATA_INT, peering_request, + "leaking", "Leaking detected", DATA_INT, leaking, + "ack_leaking", "Ack leaking", DATA_INT, ack_leaking, +// "add256", "", DATA_INT, add256, +// "add512", "", DATA_INT, add512, +// "add1024", "", DATA_INT, add1024, +// "battery_ok", "Batt OK", DATA_INT, !bat_low, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +/** @sa tpms_tyreguard400_decode() */ +static int tpms_tyreguard400_callback(r_device *decoder, bitbuffer_t *bitbuffer) +{ + //uint8_t const tyreguard_frame_sync[] = {0xf, 0xd5, 0xfd, 0x5f} + uint8_t const tyreguard_frame_sync[] = {0xfd, 0x5f, 0xd5, 0xf0}; // needs to shift sync to align bytes 28x bits usefull + + int ret = 0; + int events = 0; + + for (int row = 0; row < bitbuffer->num_rows; ++row) { + if (bitbuffer->bits_per_row[row] < TPMS_TYREGUARD400_MESSAGE_BITLEN) { + // bail out of this "too short" row early + if (decoder->verbose >= 2) { + // Output the bad row, only for message level debug / deciphering. + decoder_logf_bitrow(decoder, 2, __func__, bitbuffer->bb[row], bitbuffer->bits_per_row[row], + "Bad message in row %d need %d bits got %d", + row, TPMS_TYREGUARD400_MESSAGE_BITLEN, bitbuffer->bits_per_row[row]); + } + continue; // DECODE_ABORT_LENGTH + } + + unsigned bitpos = 0; + + // Find a preamble with enough bits after it that it could be a complete packet + while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos, tyreguard_frame_sync, 28)) + TPMS_TYREGUARD400_MESSAGE_BITLEN <= + bitbuffer->bits_per_row[row]) { + + if (decoder->verbose >= 2) { + decoder_logf_bitrow(decoder, 2, __func__, bitbuffer->bb[row], bitbuffer->bits_per_row[row], + "Find bitpos with preamble row %d at %u", row, bitpos); + } + + ret = tpms_tyreguard400_decode(decoder, bitbuffer, row, bitpos); + if (ret > 0) + events += ret; + + bitpos += TPMS_TYREGUARD400_MESSAGE_BITLEN; + } + } + // (Only) for future regression tests. + if ((decoder->verbose >= 3) & (events == 0)) { + decoder_logf(decoder, 3, __func__, "Bad transmission"); + } + + return events > 0 ? events : ret; +} + +static char *output_fields[] = { + "model", + "type", + "id", +// "flags", + "pressure_kPa", + "temperature_C", + "peering_request", + "leaking", + "ack_leaking", +// "add256", +// "add512", +// "add1024", +// "battery_ok", + "mic", + NULL, +}; + +r_device tpms_tyreguard400 = { + .name = "TyreGuard 400 TPMS", + .modulation = OOK_PULSE_MANCHESTER_ZEROBIT, + .short_width = 100, + .long_width = 100, + .gap_limit = 0, + .reset_limit = 500, + .decode_fn = &tpms_tyreguard400_callback, + .fields = output_fields, +}; From 0f673bc7bc2b16e91706ba9ab005a1ed93d4b77b Mon Sep 17 00:00:00 2001 From: Jens Klein Date: Mon, 9 May 2022 19:54:18 +0200 Subject: [PATCH 11/14] Add decoder conf for ELRO AB440 remote (closes #2066) --- conf/elro_ab440r.conf | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 conf/elro_ab440r.conf diff --git a/conf/elro_ab440r.conf b/conf/elro_ab440r.conf new file mode 100644 index 0000000000..d89d38600f --- /dev/null +++ b/conf/elro_ab440r.conf @@ -0,0 +1,39 @@ +# ELRO AB440R remote control. + +# Remote switch to turn on or off power sockets. +# The remote control has 8 buttons to control 4 sockets (on and off button) +# and 5 dip switches to dial in a unique local channel (0-31) +# +# User manual: https://www.libble.eu/elro-ab440-series/online-manual-313854/ +# +# Payload format: +# +# 1C1C1C1C1C 1B1B1B1B 10 1S1S 10000000 +# +# CCCCC: 5 bit channel number (reversed) +# BBBB: 1000 = button A +# 0100 = button B +# 0010 = button C +# 0001 = button D +# SS: 10 = ON +# 01 = OFF +# +# Test: +# rtl_433 -c conf/elro_ab440r.conf -y '{25}bbabae8' +# + +decoder { + name=ELRO-AB440R, + modulation=OOK_PWM, + short=330, + long=970, + gap=1200, + reset=9000, + bits=25, + symbol_zero={2}8, + symbol_one={2}c, + get=@0:{5}:channel, + get=@5:{4}:button:[8:A 4:B 2:C 1:D], + get=@10:{2}:toggle:[2:ON 1:OFF], + unique +} From 8f496e09c4096c18badeb5127cb70d60df2cf9cc Mon Sep 17 00:00:00 2001 From: Lasse Reinhold Date: Sat, 21 Jan 2023 20:38:09 +0100 Subject: [PATCH 12/14] Add support for Kia Rio III (UB) and Hyundai TPMS sensors (#2083) --- include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/tpms_kia.c | 155 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/devices/tpms_kia.c diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index ad75f3d4e4..b189127c2b 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -233,6 +233,7 @@ DECL(badger_orion) \ DECL(geo_minim) \ DECL(tpms_tyreguard400) \ + DECL(tpms_kia) \ /* Add new decoders here. */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c219d7f193..dc01a288e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -221,6 +221,7 @@ add_library(r_433 STATIC devices/tpms_hyundai_vdo.c devices/tpms_jansite.c devices/tpms_jansite_solar.c + devices/tpms_kia.c devices/tpms_pmv107j.c devices/tpms_porsche.c devices/tpms_renault.c diff --git a/src/devices/tpms_kia.c b/src/devices/tpms_kia.c new file mode 100644 index 0000000000..35b30ec495 --- /dev/null +++ b/src/devices/tpms_kia.c @@ -0,0 +1,155 @@ +/** @file + Kia Rio UB III (UB) 2011-2017 TPMS sensor and some Hyundai models too. + + Copyright (C) 2022 Lasse Mikkel Reinhold, Todor Uzunov aka teou, TTiges, 2019 Andreas + Spiess, 2017 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. +*/ + +/** +TPMS sensor for Kio Rio III (UB) 2011-2017 and some Hyundai models. Possibly other brands and +models too. + +The sensors have accelerometers that sense the centripetal force in a spinning wheel and begin +to transmit data around 40 km/h. They usually keep transmitting for several minutes after +stopping, but not always; on a few rare occasions they stop instantly. Each sensor sends a +burst of 4-6 packets two times a minutes. The packets in a burst are often, but not always, +identical. + +154 bits in a packet. Bit layout (leftmost bit in a field is the most significant): + zzzzzzzzzzzzzzzz aaaa pppppppp tttttttt iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii dddddddd ccccc + +Legend: + z: 16-bit preamble = 0xed71. Must be omitted from Manchester-decoding + a: Unknown, but 0xf in all my own readings + p: 8-bit pressure given as PSI * 5 + t: 8-bit temperature given as Celcius + 50 + i: 32-bit Sensor ID + d: Unknown, with different value in each packet + c: First 5 bits of CRC. We need to append 000 to reach 8 bits. poly=0x07, init=0x76. + +NOTE: I often get pressure and temperature values that are outliers (like 200 C or 10 PSI) from +all four sensors, even when CRC is OK. I don't know if all my sensors are defunct or if I have +missed something in the encoding. I have included a "raw" field to make it easier for other +users to investigate it. + +NOTE: You may need to use the "-s 1000000" option of rtl_433 in order to get a clear signal. + */ + +#include "decoder.h" + +static int tpms_kia_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos) +{ + data_t *data; + bitbuffer_t packet_bits = {0}; + uint8_t *b; + unsigned id; + char id_str[9 + 1]; + char unknown1_str[2 + 1]; + char unknown2_str[3 + 1]; + uint8_t unknown1; + uint8_t unknown2; + uint8_t pressure; + + uint8_t temperature; + uint8_t crc; + char raw[9 * 2 + 1]; // 9 bytes in hex notation + unsigned int start_pos; + const unsigned int preamble_length = 16; + + start_pos = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 154 - preamble_length); + if (start_pos - bitpos < 154 - preamble_length) { + return DECODE_ABORT_LENGTH; + } + + b = packet_bits.bb[0]; + + unknown1 = b[0] >> 4; + pressure = b[0] << 4 | b[1] >> 4; + temperature = b[1] << 4 | b[2] >> 4; + id = b[2] << 28 | b[3] << 20 | b[4] << 12 | b[5] << 4 | b[6] >> 4; + unknown2 = b[6] << 8 | b[7]; + + // The last 3 bits in b[8] are beyond the packet length of 154 bits. Make them 000. + crc = b[8] & (~0x7); + uint8_t c = crc8(b, 8, 0x07, 0x76); + if (crc != c) { + return DECODE_FAIL_MIC; + } + + sprintf(id_str, "%08x", id); + sprintf(unknown1_str, "%02x", unknown1); + sprintf(unknown2_str, "%03x", unknown2); + sprintf(raw, "%02x%02x%02x%02x%02x%02x%02x%02x%02x", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]); + + float pressure_float = pressure / 5.0; + float temperature_float = temperature - 50.0; + + /* clang-format off */ + data = data_make( + "model", "", DATA_STRING, "Kia", + "type", "", DATA_STRING, "TPMS", + "id", "", DATA_STRING, id_str, + "unknown1", "", DATA_STRING, unknown1_str, + "unknown2", "", DATA_STRING, unknown2_str, + "pressure_PSI", "pressure", DATA_FORMAT, "%.1f PSI", DATA_DOUBLE, pressure_float, + "temperature_C", "temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, temperature_float, + "raw", "", DATA_STRING, raw, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +/** +Wrapper for the Kia tpms. +@sa tpms_kia_decode() +*/ +static int tpms_kia_callback(r_device *decoder, bitbuffer_t *bitbuffer) +{ + uint8_t const preamble_pattern[2] = {0xed, 0x71}; + const int preamble_length = 16; + + unsigned bitpos = 0; + int ret = 0; + int events = 0; + + // Find a preamble with enough bits after it that it could be a complete packet + while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, preamble_length)) + 154 <= bitbuffer->bits_per_row[0]) { + ret = tpms_kia_decode(decoder, bitbuffer, 0, bitpos + preamble_length); + if (ret > 0) { + events += ret; + } + bitpos += 2; + } + + return events > 0 ? events : ret; +} + +static char *output_fields[] = { + "model", + "type", + "id", + "unknown1", + "unknown2", + "pressure_PSI", + "temperature_C", + "raw", + "mic", + NULL, +}; + +r_device tpms_kia = { + .name = "Kia TPMS (-s 1M)", + .modulation = FSK_PULSE_PCM, + .short_width = 50, + .long_width = 50, + .reset_limit = 200, + .decode_fn = &tpms_kia_callback, + .fields = output_fields, +}; From 7abfe031a2d8f28563e26be2c70ad220bc6c0480 Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Sat, 21 Jan 2023 20:39:03 +0100 Subject: [PATCH 13/14] minor: Update docs --- README.md | 1 + conf/rtl_433.example.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 2eac31611b..f519c5ed28 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [223] Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) [224] GEO minim+ energy monitor [225] TyreGuard 400 TPMS + [226] Kia TPMS (-s 1M) * 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 20b11387d0..5b2b6817cc 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -447,6 +447,7 @@ stop_after_successful_events false protocol 223 # Badger ORION water meter, 100kbps (-f 916450000 -s 1200000) protocol 224 # GEO minim+ energy monitor protocol 225 # TyreGuard 400 TPMS + protocol 226 # Kia TPMS (-s 1M) ## Flex devices (command line option "-X") From e37fc5dd3854ca7e95099a9cf6410357701c3089 Mon Sep 17 00:00:00 2001 From: stevieg2123 Date: Sat, 21 Jan 2023 12:43:55 -0700 Subject: [PATCH 14/14] Add ESIC/SCMplus fields to rtl_433_mqtt_hass (#2114) Adding power meter energy/current/voltage values, as well as consumption from SCMplus meters (water/gas) --- examples/rtl_433_mqtt_hass.py | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/examples/rtl_433_mqtt_hass.py b/examples/rtl_433_mqtt_hass.py index dbce40b135..a448b1247b 100755 --- a/examples/rtl_433_mqtt_hass.py +++ b/examples/rtl_433_mqtt_hass.py @@ -472,6 +472,42 @@ "state_class": "measurement" } }, + + "energy_kWh": { + "device_type": "sensor", + "object_suffix": "kwh", + "config": { + "device_class": "power", + "name": "Energy", + "unit_of_measurement": "kWh", + "value_template": "{{ value|float }}", + "state_class": "measurement" + } + }, + + "current_A": { + "device_type": "sensor", + "object_suffix": "A", + "config": { + "device_class": "power", + "name": "Current", + "unit_of_measurement": "A", + "value_template": "{{ value|float }}", + "state_class": "measurement" + } + }, + + "voltage_V": { + "device_type": "sensor", + "object_suffix": "V", + "config": { + "device_class": "power", + "name": "Voltage", + "unit_of_measurement": "V", + "value_template": "{{ value|float }}", + "state_class": "measurement" + } + }, "light_lux": { "device_type": "sensor", @@ -556,6 +592,16 @@ "state_class": "total_increasing", } }, + + "consumption": { + "device_type": "sensor", + "object_suffix": "consumption", + "config": { + "name": "SCMplus Consumption Value", + "value_template": "{{ value|int }}", + "state_class": "total_increasing", + } + }, "channel": { "device_type": "device_automation",