diff --git a/README.md b/README.md index f2b66339d..847a024d9 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [153] Cotech 36-7959, SwitchDocLabs FT020T wireless weather station with USB [154] Standard Consumption Message Plus (SCMplus) [155] Fine Offset Electronics WH1080/WH3080 Weather Station (FSK) - [156] Abarth 124 Spider TPMS + [156] Abarth 124 Spider and Shenzhen EGQ Q85 TPMS [157] Missil ML0757 weather station [158] Sharp SPC775 weather station [159] Insteon @@ -329,6 +329,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [243] Celsia CZC1 Thermostat [244] Fine Offset Electronics WS90 weather station [245]* ThermoPro TX-2C Thermometer + [246] TFA 30.3151 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 bd15e84b7..1d18eebb1 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -253,6 +253,7 @@ DECL(celsia_czc1) \ DECL(fineoffset_ws90) \ DECL(thermopro_tx2c) \ + DECL(tfa_303151) \ /* Add new decoders here. */ diff --git a/src/devices/fineoffset_wh1050.c b/src/devices/fineoffset_wh1050.c index f366ae30c..0aefdf1e3 100644 --- a/src/devices/fineoffset_wh1050.c +++ b/src/devices/fineoffset_wh1050.c @@ -1,8 +1,9 @@ /** @file - Fine Offset WH1050 Weather Station. + Fine Offset WH1050 and TFA 30.3151 Weather Station. 2016 Nicola Quiriti ('ovrheat') Modifications 2016 by Don More + 2023 Bruno OCTAU (ProfBoc75) for TFA 30.3151 FSK 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 @@ -12,9 +13,11 @@ */ #include "decoder.h" +#define TYPE_OOK 1 +#define TYPE_FSK 2 -/** -Fine Offset WH1050 Weather Station. +/** @fn in fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned bitpos, int type) +Fine Offset WH1050 and TFA 30.3151 Weather Station. This module is a cut-down version of the WH1080 decoder. The WH1050 sensor unit is like the WH1080 unit except it has no @@ -44,35 +47,43 @@ around the minute 59 of the even hours the sensor's TX stops sending weather dat After around 3-4 minutes of silence it starts to send just time data for some minute, then it starts again with weather data as usual. +TFA 30.3151 Sensor is FSK version and decodes here. See issue #2538: Preamble is aaaa2dd4 and Temperature is not offset and rain gauge is 0.5 mm by pulse. + To recognize which message is received (weather or time) you can use the 'msg_type' field on json output: - msg_type 0 = weather data - msg_type 1 = time data Weather data - Message layout and example: - AA BC CD DD EE FF GG HH HH II - {80} ff 5f 51 93 48 00 00 12 46 aa + Preamble{8} : 0xFF - OOK Version + or Preamble{40} : 0xAAAAAA2DD4 - FSK Version + + Byte Position : 00 01 02 03 04 05 06 07 08 + Payload{72} : BC CD DD EE FF GG HH HH II + Sample{72} : 5f 51 93 48 00 00 12 46 aa -- A : 8 bits : Preamble 0xFF - B : 4 bits : ?? - seems to be 0x5 for whether data, 0x6 for time data - C : 8 bits : Id, changes when reset (e.g., 0xF5) - D : 1 bit : msg_type - 0, for whether data - D : 1 bit : Battery, 0 = ok, 1 = low (e.g, OK) -- D : 10 bits : Temperature in Celsius, offset 400, scaled by 10 (e.g., 0.3 degrees C) +- D : 10 bits : Temperature in Celsius, [offset 400 only for OOK Version], scaled by 10 (e.g., 0.3 degrees C) - E : 8 bits : Relative humidity, percent (e.g., 72%) - F : 8 bits : Wind speed average in m/s, scaled by 1/0.34 (e.g., 0 m/s) - G : 8 bits : Wind speed gust in m/s, scaled by 1/0.34 (e.g., 0 m/s) -- H : 16 bits : Total rainfall in units of 0.3mm, since reset (e.g., 1403.4 mm) +- H : 16 bits : Total rainfall in units of 0.3mm (OOK version) or 0.5mm (FSK version), since reset (e.g., 1403.4 mm) - I : 8 bits : CRC, poly 0x31, init 0x00 (excluding preamble) Time data - Message layout and example: - AA BC CD DE FG HI JK LM NO PP - {80} ff 69 0a 96 02 41 23 43 27 df + Preamble{8} : 0xFF - OOK Version + or Preamble{40} : 0xAAAAAA2DD4 - FSK Version + + Byte Position : 00 01 02 03 04 05 06 07 08 + Payload{72} : BC CD DE FG HI JK LM NO PP + Sample{72} : 69 0a 96 02 41 23 43 27 df -- A : 8 bits : Preamble 0xFF - B : 4 bits : ?? - seems to be 0x5 for whether data, seems 0x6 for time data -- C : 8 bits : Id, changes when reset (e.g., 0xF5) +- C : 8 bits : Id, changes when reset (e.g., 0x90) - D : 1 bit : msg_type - 1, for time data - D : 1 bit : Battery, 0 = ok, 1 = low (e.g, OK) - D : 4 bits : ?? @@ -92,38 +103,14 @@ Time data - Message layout and example: - P : 8 bits : CRC, poly 0x31, init 0x00 (excluding preamble) */ -static int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer) +static int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned bitpos, int type) { data_t *data; uint8_t br[9]; + float temperature, rain; - if (bitbuffer->num_rows != 1) { - return DECODE_ABORT_EARLY; - } + bitbuffer_extract_bytes(bitbuffer, 0, bitpos, br, 9 * 8); - /* The normal preamble for WH1050 is 8 1s (0xFF) followed by 4 0s - for a total 80 bit message. - (The 4 0s is not confirmed to be preamble but seems to be zero on most devices) - - Digitech XC0346 (and possibly other models) only sends 7 1 bits not 8 (0xFE) - for some reason (maybe transmitter module is slow to wake up), for a total - 79 bit message. - - In both cases, we extract the 72 bits after the preamble. - */ - unsigned bits = bitbuffer->bits_per_row[0]; - uint8_t preamble_byte = bitbuffer->bb[0][0]; - if (bits == 79 && preamble_byte == 0xfe) { - bitbuffer_extract_bytes(bitbuffer, 0, 7, br, 72); - } else if (bits == 80 && preamble_byte == 0xff) { - bitbuffer_extract_bytes(bitbuffer, 0, 8, br, 72); - } else { - return DECODE_ABORT_LENGTH; - } - - // If you calculate the CRC over all 10 bytes including the preamble - // byte (always 0xFF), then CRC_INIT is 0xFF. But we compare the preamble - // byte and then discard it. if (crc8(br, 9, 0x31, 0x00)) { return DECODE_FAIL_MIC; // crc mismatch } @@ -134,26 +121,33 @@ static int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer) if (msg_type == 0) { // GETTING WEATHER SENSORS DATA int temp_raw = ((br[1] & 0x03) << 8) | br[2]; - float temperature = (temp_raw - 400) * 0.1f; + int rain_raw = (br[6] << 8) | br[7]; + if (type == TYPE_OOK) { + temperature = (temp_raw - 400) * 0.1f; + rain = rain_raw * 0.3f; + } + else { + temperature = temp_raw * 0.1f; + rain = rain_raw * 0.5f; + } int humidity = br[3]; float speed = (br[4] * 0.34f) * 3.6f; // m/s -> km/h float gust = (br[5] * 0.34f) * 3.6f; // m/s -> km/h - int rain_raw = (br[6] << 8) | br[7]; - float rain = rain_raw * 0.3f; int device_id = (br[0] << 4 & 0xf0) | (br[1] >> 4); int battery_low = br[1] & 0x04; /* clang-format off */ data = data_make( - "model", "", DATA_STRING, "Fineoffset-WH1050", - "id", "Station ID", DATA_FORMAT, "%04X", DATA_INT, device_id, + "model", "", DATA_COND, type == TYPE_OOK, DATA_STRING, "Fineoffset-WH1050", + "model", "", DATA_COND, type == TYPE_FSK, DATA_STRING, "TFA-303151", + "id", "Station ID", DATA_FORMAT, "%02X", DATA_INT, device_id, "msg_type", "Msg type", DATA_INT, msg_type, "battery_ok", "Battery", DATA_INT, !battery_low, "temperature_C", "Temperature", DATA_FORMAT, "%.01f C", DATA_DOUBLE, temperature, "humidity", "Humidity", DATA_FORMAT, "%u %%", DATA_INT, humidity, - "wind_avg_km_h", "Wind avg speed", DATA_FORMAT, "%.02f", DATA_DOUBLE, speed, - "wind_max_km_h", "Wind gust", DATA_FORMAT, "%.02f", DATA_DOUBLE, gust, - "rain_mm", "Total rainfall", DATA_FORMAT, "%.01f", DATA_DOUBLE, rain, + "wind_avg_km_h", "Wind avg speed", DATA_FORMAT, "%.02f km/h", DATA_DOUBLE, speed, + "wind_max_km_h", "Wind gust", DATA_FORMAT, "%.02f km/h ", DATA_DOUBLE, gust, + "rain_mm", "Total rainfall", DATA_FORMAT, "%.01f mm", DATA_DOUBLE, rain, "mic", "Integrity", DATA_STRING, "CRC", NULL); /* clang-format on */ @@ -175,8 +169,9 @@ static int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer) /* clang-format off */ data = data_make( - "model", "", DATA_STRING, "Fineoffset-WH1050", - "id", "Station ID", DATA_INT, device_id, + "model", "", DATA_COND, type == TYPE_OOK, DATA_STRING, "Fineoffset-WH1050", + "model", "", DATA_COND, type == TYPE_FSK, DATA_STRING, "TFA-303151", + "id", "Station ID", DATA_FORMAT, "%02X", DATA_INT, device_id, "msg_type", "Msg type", DATA_INT, msg_type, "battery_ok", "Battery", DATA_INT, !battery_low, "radio_clock", "Radio Clock", DATA_STRING, clock_str, @@ -189,6 +184,52 @@ static int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer) return 1; } +/** +Fineoffset or TFA OOK/FSK protocol. +@sa fineoffset_wh1050_decode() +*/ +static int fineoffset_wh1050_callback(r_device *decoder, bitbuffer_t *bitbuffer) +{ + unsigned bitpos = 0; + int events = 0; + + if (bitbuffer->num_rows != 1) { + return DECODE_ABORT_EARLY; + } + + /* The normal preamble for WH1050 is 8 1s (0xFF) followed by 4 0s + for a total 80 bit message. + (The 4 0s is not confirmed to be preamble but seems to be zero on most devices) + + Digitech XC0346 (and possibly other models) only sends 7 1 bits not 8 (0xFE) + for some reason (maybe transmitter module is slow to wake up), for a total + 79 bit message. + + In both cases, we extract the 72 bits after the preamble. + + For FSK version TFA 30.3151 the preamble is aaaaaa2dd4 and message payload is 6 times repeats (gap, preamble, message, gap, ... ) in one row and 754 bits. + gap is 11 bits long, preamble need to be searched into a while loop to get the repeated message + */ + + unsigned bits = bitbuffer->bits_per_row[0]; + uint8_t preamble_byte = bitbuffer->bb[0][0]; // for OOK + uint8_t const preamble_fsk[] = {0xAA, 0x2D, 0xD4}; // part of preamble and sync word for FSK + if (bits == 79 && preamble_byte == 0xfe) { + fineoffset_wh1050_decode(decoder, bitbuffer, 7, TYPE_OOK); + } else if (bits == 80 && preamble_byte == 0xff) { + fineoffset_wh1050_decode(decoder, bitbuffer, 8, TYPE_OOK); + } else if (bits > 112 && bits < 760) { + while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_fsk, sizeof(preamble_fsk) * 8)) + 72 <= + bitbuffer->bits_per_row[0]) { + events += fineoffset_wh1050_decode(decoder, bitbuffer, bitpos + sizeof(preamble_fsk) * 8, TYPE_FSK); + bitpos += 123; + } + } else { + return DECODE_ABORT_LENGTH; + } + return events; +} + static char const *const output_fields[] = { "model", "id", @@ -210,6 +251,16 @@ r_device const fineoffset_wh1050 = { .short_width = 544, .long_width = 1524, .reset_limit = 10520, - .decode_fn = &fineoffset_wh1050_decode, + .decode_fn = &fineoffset_wh1050_callback, + .fields = output_fields, +}; + +r_device const tfa_303151 = { + .name = "TFA 30.3151 Weather Station", + .modulation = FSK_PULSE_PCM, + .short_width = 60, + .long_width = 60, + .reset_limit = 2500, + .decode_fn = &fineoffset_wh1050_callback, .fields = output_fields, }; diff --git a/src/devices/tpms_abarth124.c b/src/devices/tpms_abarth124.c index d4c56c7fd..9ad07199c 100644 --- a/src/devices/tpms_abarth124.c +++ b/src/devices/tpms_abarth124.c @@ -1,5 +1,8 @@ -/** @file - VDO Type TG1C FSK 9 byte Manchester encoded checksummed TPMS data. +/** @file + VDO TPMS Type TG1C and Q85. + Copyright (C) TTigges for (VDO Type TG1C via) Abarth 124 Spider TPMS decoded + Protocol similar (and based on) Jansite Solar TPMS by Andreas Spiess and Christian W. Zuckschwerdt + Copyright (C) 2023 Bruno OCTAU (ProfBoc75) Add Shenzhen EGQ Q85 support 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 @@ -8,9 +11,11 @@ */ /** -(VDO Type TG1C via) Abarth 124 Spider TPMS decoded by TTigges -Protocol similar (and based on) Jansite Solar TPMS by Andreas Spiess and Christian W. Zuckschwerdt +VDO TPMS Type TG1C and Q85. +VDO Type TG1C FSK 9 byte Manchester encoded, checksummed for 8 bytes TPMS data. +Q85 from Shenzhen EGQ Cloud technology CO,LTD, FSK 12 byte Manchester encoded, checksummed for 8 bytes TPMS data + CRC/16 CCITT-FASLE for 10 bytes DATA. +TG1C (Abarth-124Spider): OEM Sensor is said to be a VDO Type TG1C, available in different cars, e.g.: Abarth 124 Spider, some Fiat 124 Spider, some Mazda MX-5 ND (and NC?) and probably some other Mazdas. Mazda reference/part no.: BHB637140A @@ -22,46 +27,87 @@ Compatible with aftermarket sensors, e.g. Aligator sens.it RS3 // Working Frequency: 433.92MHz+-38KHz // Tire monitoring range value: 0kPa-350kPa+-7kPa (to be checked, VDO says 450/900kPa) +Q85 (Shenzhen-EGQ-Q85): +Analysis asked by @js-3972 in issue #2556 +TPMS from Amazon here: https://www.amazon.com/dp/B0BK8SHDRZ?psc=1&ref=ppx_yo2ov_dt_b_product_details +Air pressure setting range: 0.1~6.0BA / 1.45~87psi +Temperature setting range: 60°C~110°C / 140ºF~230ºF +Working temperature: -20°C~80°C / -68ºF~176ºF +Storage temperature: -30°C~85°C / 86ºF~185ºF +Power-on voltage: DC 5V +Frequency: 433.92MHz±20.00MHZ (remark: more probably ±20.00KHZ than MHZ) + +Very similar to Abarth 124Spider, message lengh is bigger including a 0x40 then a CRC/16 CCITT-FALSE, (LSB first, then MSB) +Temperature (°C) offset is 55 C +Pressure (KPa) is divided by 3. + +Both payload: + +- The preamble is 0xaa..aa9 (or 0x55..556 depending on polarity) + Data layout (nibbles): - II II II II ?? PP TT SS CC + +Byte 00 01 02 03 04 05 06 07 08 09 10 11 +TG1C II II II II ?? PP TT SS CC +Q85 II II II II ?? PP TT SS CC FF CR CR + - I: 32 bit ID - ?: 4 bit unknown (seems to change with status) - ?: 4 bit unknown (seems static) -- P: 8 bit Pressure (multiplyed by 1.38 = kPa) -- T: 8 bit Temperature (deg. C offset by 50) +- P: 8 bit Pressure (multiplyed by 1.38 = kPa for TG1C, by 3 for Q85) +- T: 8 bit Temperature (deg. C offset by 50 for TG1C, by 55 for Q85) - S: Status? (first nibble seems static, second nibble seems to change with status) -- C: 8 bit Checksum (Checksum8 XOR on bytes 0 to 8) -- The preamble is 0xaa..aa9 (or 0x55..556 depending on polarity) +- CC: 8 bit Checksum (Checksum8 XOR on bytes 0 to 8) +- F: 8 bit unknown (Q85 only, seems fixed = 0x40) +- CR: 16 bit CRC/16 CCITT-FASLE. little-Endian format (Q85 only, on bytes 0 to 9). */ #include "decoder.h" +#define MODEL_TG1C 1 +#define MODEL_Q85 2 -static int tpms_abarth124_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos) +static int tpms_abarth124_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos, int type) { data_t *data; bitbuffer_t packet_bits = {0}; uint8_t *b; char id_str[4 * 2 + 1]; char flags[1 * 2 + 1]; - int pressure; - int temperature; - int status; - int checksum; + int pressure, temperature, status, checksum, data_len; + uint16_t crc_little_endian, crc_result; + + // 9 byte or 12 byte + if (type == MODEL_TG1C) { + data_len = 72; + } else if (type == MODEL_Q85) { + data_len = 96; + } else { + return 0; //return 0 instead of DECODE_ABORT_LENGTH, to avoid exit error : gave invalid return value -x: notify maintainer; + } - bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 72); + bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, data_len); // make sure we decoded the expected number of bits - if (packet_bits.bits_per_row[0] < 72) { + if (packet_bits.bits_per_row[0] < data_len) { // decoder_logf(decoder, 0, __func__, "bitpos=%u start_pos=%u = %u", bitpos, start_pos, (start_pos - bitpos)); - return 0; // DECODE_FAIL_SANITY; + return 0; //DECODE_FAIL_SANITY; } b = packet_bits.bb[0]; - // check checksum (checksum8 xor) + // check checksum (checksum8 xor) same for both type model checksum = xor_bytes(b, 9); if (checksum != 0) { - return 0; // DECODE_FAIL_MIC; + return 0; //DECODE_FAIL_MIC; + } + + if (type == MODEL_Q85) { + // check CRC for 0 to 9 byte only if type Q85, CRC 16 CCITT-FALSE little-endian + crc_little_endian = (b[11] << 8 ) | b[10]; + crc_result = crc16(b, 10, 0x1021,0xffff); // CRC-16 CCITT-FALSE + if (crc_result != crc_little_endian) { + return 0; //DECODE_FAIL_MIC; + } } sprintf(flags, "%02x", b[4]); @@ -73,14 +119,18 @@ static int tpms_abarth124_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsi /* clang-format off */ data = data_make( - "model", "", DATA_STRING, "Abarth-124Spider", + "model", "", DATA_COND, type == MODEL_TG1C, DATA_STRING, "Abarth-124Spider", + "model", "", DATA_COND, type == MODEL_Q85, DATA_STRING, "Shenzhen-EGQ-Q85", "type", "", DATA_STRING, "TPMS", "id", "", DATA_STRING, id_str, "flags", "", DATA_STRING, flags, - "pressure_kPa", "Pressure", DATA_FORMAT, "%.0f kPa", DATA_DOUBLE, (float)pressure * 1.38, - "temperature_C", "Temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, (float)temperature - 50.0, + "pressure_kPa", "Pressure", DATA_COND, type == MODEL_TG1C, DATA_FORMAT, "%.0f kPa", DATA_DOUBLE, (float)pressure * 1.38, + "pressure_kPa", "Pressure", DATA_COND, type == MODEL_Q85, DATA_FORMAT, "%.0f kPa", DATA_DOUBLE, (float)pressure * 3, + "temperature_C", "Temperature", DATA_COND, type == MODEL_TG1C, DATA_FORMAT, "%.0f C", DATA_DOUBLE, (float)temperature - 50.0, + "temperature_C", "Temperature", DATA_COND, type == MODEL_Q85, DATA_FORMAT, "%.0f C", DATA_DOUBLE, (float)temperature - 55.0, "status", "", DATA_INT, status, - "mic", "Integrity", DATA_STRING, "CHECKSUM", + "mic", "Integrity", DATA_COND, type == MODEL_TG1C, DATA_STRING, "CHECKSUM", + "mic", "Integrity", DATA_COND, type == MODEL_Q85, DATA_STRING, "CRC", NULL); /* clang-format on */ @@ -96,12 +146,24 @@ static int tpms_abarth124_callback(r_device *decoder, bitbuffer_t *bitbuffer) unsigned bitpos = 0; int events = 0; + int type = 0; bitbuffer_invert(bitbuffer); + unsigned bits = bitbuffer->bits_per_row[0]; + + // Define the type model , around 195 bits for TG1C MC encoded (result is 72 bits), around 353 bits for Q85 MC encoded (result is 96 bits) + if (bits > 150 && bits < 210) { + type = MODEL_TG1C; + } else if ( bits > 210 && bits < 400) { + type = MODEL_Q85; + } else { + return DECODE_ABORT_LENGTH; + } + // Find a preamble with enough bits after it that it could be a complete packet while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24)) + 80 <= bitbuffer->bits_per_row[0]) { - events += tpms_abarth124_decode(decoder, bitbuffer, 0, bitpos + 24); + events += tpms_abarth124_decode(decoder, bitbuffer, 0, bitpos + 24,type); bitpos += 2; } @@ -122,7 +184,7 @@ static char const *const output_fields[] = { }; r_device const tpms_abarth124 = { - .name = "Abarth 124 Spider TPMS", + .name = "Abarth 124 Spider and Shenzhen EGQ Q85 TPMS", .modulation = FSK_PULSE_PCM, .short_width = 52, // 12-13 samples @250k .long_width = 52, // FSK