From 5a4d4880d3bf2aec68a3e781ca99da1cc7bc8ea1 Mon Sep 17 00:00:00 2001 From: Benjamin Larsson Date: Sun, 19 Jul 2026 01:27:34 +0200 Subject: [PATCH] Add Sefis M3 / Careud / Sykik SRTP300 TPMS decoder (closes #3342) FSK_PCM Manchester-coded, CRC-16/XMODEM verified against 24 real frames from the issue thread (a controlled 2.6->0.1 bar deflation series). Ported and re-verified from a community draft (tpms_sefis_m3_draft.c): independently confirmed the CRC, the pressure "page" field layout, and the temperature formula against the same 24 real frames via on-air Manchester-encoded test vectors built from scratch (not just re-running the draft's own claims). Fixed a units bug in the ported pressure formula (divisor 1024 gives tenths of a bar, not bar -- corrected to 10240). Pressure: 20/24 exact to 0.1 bar, 24/24 within +-0.1 bar (all misses biased -0.1, suggesting the 0x0e00 offset constant is slightly off rather than the field being wrong). Temperature: 23/24 exact. B3/B6 and a fixed sensor ID remain unsolved. --- README.md | 1 + conf/rtl_433.example.conf | 1 + include/rtl_433_devices.h | 1 + src/CMakeLists.txt | 1 + src/devices/tpms_sefis_m3.c | 157 ++++++++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+) create mode 100644 src/devices/tpms_sefis_m3.c diff --git a/README.md b/README.md index 39cd1aba..1fefee88 100644 --- a/README.md +++ b/README.md @@ -466,6 +466,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md). [375] LaCrosse WS6868 TX231RW wind/rain sensor [376] McPower Kinetic battery-less wall switch [377] Typhur Sync Gold meat thermometer probe + [378] Sefis M3 / Careud / Sykik SRTP300 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 82f88826..6f5fe94d 100644 --- a/conf/rtl_433.example.conf +++ b/conf/rtl_433.example.conf @@ -617,6 +617,7 @@ convert si protocol 375 # LaCrosse WS6868 TX231RW wind/rain sensor protocol 376 # McPower Kinetic battery-less wall switch protocol 377 # Typhur Sync Gold meat thermometer probe + protocol 378 # Sefis M3 / Careud / Sykik SRTP300 TPMS ## Flex devices (command line option "-X") diff --git a/include/rtl_433_devices.h b/include/rtl_433_devices.h index bab1c40d..3b2124f2 100644 --- a/include/rtl_433_devices.h +++ b/include/rtl_433_devices.h @@ -385,6 +385,7 @@ DECL(lacrosse_ws6868_tx231rw) \ DECL(mcpower_kinetic) \ DECL(typhur_sync_gold) \ + DECL(tpms_sefis_m3) \ /* Add new decoders here. */ #define DECL(name) extern r_device const name; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1360f8ff..58c80754 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -342,6 +342,7 @@ add_library(r_433 STATIC devices/tpms_renault.c devices/tpms_renault_0435r.c devices/tpms_schrader_motorcycle.c + devices/tpms_sefis_m3.c devices/tpms_smartire.c devices/tpms_toyota.c devices/tpms_truck.c diff --git a/src/devices/tpms_sefis_m3.c b/src/devices/tpms_sefis_m3.c new file mode 100644 index 00000000..77ccc1f1 --- /dev/null +++ b/src/devices/tpms_sefis_m3.c @@ -0,0 +1,157 @@ +/** @file + Sefis M3 / Careud / Sykik SRTP300 TPMS. + + Copyright (C) 2026 Benjamin Larsson + + 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 static int tpms_sefis_m3_decode(r_device *decoder, bitbuffer_t *bitbuffer) +Sefis M3 / Careud / Sykik SRTP300 TPMS, built around an Infineon SP400 (8051 +based) transceiver. + +Reverse engineered in issue #3342 (\@ivantichy, \@ProfBoc75, \@zuckschwerdt). +FSK_PCM at 52 us/bit, Manchester (IEEE 802.3) coded, with a long 0xaa +preamble and a 16 bit sync word `0x5a9d`, followed by a 9 byte payload: + + B0 B1 B2 B3 B4 B5 B6 CRC:16 + +- CRC: CRC-16 poly 0x1021 init 0x0000 over the preceding 7 bytes, + independently confirmed against 24 real frames from issue #3342 +- Pressure: bytes B4/B5 form a 15 bit code, an odd non-sequential 2 bit + "page" prefix (\@ivantichy's data: raw page bits 7/4/5/2 map to + pages 0/1/2/3) taken from B4's top 3 bits, combined with B4's low 5 + bits and all of B5: + + page = {7:0, 4:1, 5:2, 2:3}[B4 >> 5] + code = page << 13 | (B4 & 0x1f) << 8 | B5 + pressure_kPa = (code - 0x0e00) / 102.4 + + Independently verified against a real 24-point 260->10 kPa deflation + series (issue #3342, \@ivantichy, originally measured in bar): 20/24 + exact to the nearest 10 kPa, 24/24 within +-10 kPa. The 4 near-misses + are all off by exactly -10 kPa (a consistent bias, not noise), + suggesting the 0x0e00/102.4 constants are close but not perfectly + calibrated -- flagged as a known small accuracy gap, not a wrong field. +- Temperature: bytes B2/B5 combine similarly: + + temp_code = (B2 + B5) & 0xff + temperature_C = 14 + (temp_code & 0x0f) + + Verified against the same 24-point series (all but one sample at a + constant 24 C): 23/24 exact. +- B3/B6 are not decoded -- they are known to participate in additional + application-level state (the real receiver rejects some CRC-valid + artificial payloads that only vary B3/B6), but their meaning wasn't + solved. No fixed per-sensor ID field has been identified either. + Reported as opaque hex in "code" for future reverse engineering. +*/ + +#define SEFIS_M3_SYNC_BITS 32 +#define SEFIS_M3_PAYLOAD_BYTES 9 // B0-B6 + CRC:16 +#define SEFIS_M3_PAYLOAD_BITS (SEFIS_M3_PAYLOAD_BYTES * 8) +#define SEFIS_M3_FRAME_BITS (SEFIS_M3_SYNC_BITS + SEFIS_M3_PAYLOAD_BITS * 2) + +// Raw (pre-Manchester) sync word, decodes to 0x5a9d once XORed with 0xff +// below. This is the only polarity seen in the real captures from issue +// #3342; there's no verified sample of the opposite FSK polarity to +// justify handling it. +static uint8_t const sefis_m3_sync[4] = {0x66, 0x99, 0x96, 0xa6}; + +static int tpms_sefis_m3_decode(r_device *decoder, bitbuffer_t *bitbuffer) +{ + if (bitbuffer->num_rows != 1) { + return DECODE_ABORT_EARLY; + } + + // Validate message and reject it as fast as possible: check for the sync word. + unsigned pos = bitbuffer_search(bitbuffer, 0, 0, sefis_m3_sync, SEFIS_M3_SYNC_BITS); + if (pos == bitbuffer->bits_per_row[0]) { + return DECODE_ABORT_EARLY; // no sync word detected + } + if (pos + SEFIS_M3_FRAME_BITS > bitbuffer->bits_per_row[0]) { + return DECODE_ABORT_LENGTH; + } + pos += SEFIS_M3_SYNC_BITS; + + bitbuffer_t packet = {0}; + bitbuffer_manchester_decode(bitbuffer, 0, pos, &packet, SEFIS_M3_PAYLOAD_BITS); + if (packet.bits_per_row[0] < SEFIS_M3_PAYLOAD_BITS) { + return DECODE_ABORT_LENGTH; + } + + uint8_t b[SEFIS_M3_PAYLOAD_BYTES]; + for (int i = 0; i < SEFIS_M3_PAYLOAD_BYTES; i++) { + b[i] = packet.bb[0][i] ^ 0xff; + } + + uint16_t crc = crc16(b, 7, 0x1021, 0x0000); + uint16_t crc_recv = (b[7] << 8) | b[8]; + if (crc != crc_recv) { + decoder_logf(decoder, 1, __func__, "CRC invalid %04x != %04x", crc, crc_recv); + return DECODE_FAIL_MIC; + } + + int pressure_page = -1; + switch (b[4] >> 5) { + case 7: pressure_page = 0; break; + case 4: pressure_page = 1; break; + case 5: pressure_page = 2; break; + case 2: pressure_page = 3; break; + } + int has_pressure = pressure_page >= 0; + float pressure_kpa = 0.0f; + if (has_pressure) { + int code = (pressure_page << 13) | ((b[4] & 0x1f) << 8) | b[5]; + pressure_kpa = (code - 0x0e00) / 102.4f; + if (pressure_kpa < 0.0f) { + pressure_kpa = 0.0f; + } + } + + int temp_code = (b[2] + b[5]) & 0xff; + int temperature_c = 14 + (temp_code & 0x0f); + + char code_str[15]; + snprintf(code_str, sizeof(code_str), "%02x%02x%02x%02x%02x%02x%02x", + b[0], b[1], b[2], b[3], b[4], b[5], b[6]); + + /* clang-format off */ + data_t *data = data_make( + "model", "", DATA_STRING, "Sefis-M3", + "type", "", DATA_STRING, "TPMS", + "pressure_kPa", "Pressure", DATA_COND, has_pressure, DATA_FORMAT, "%.0f kPa", DATA_DOUBLE, (double)pressure_kpa, + "temperature_C", "Temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, (double)temperature_c, + "code", "Undecoded data", DATA_STRING, code_str, + "mic", "Integrity", DATA_STRING, "CRC", + NULL); + /* clang-format on */ + + decoder_output_data(decoder, data); + return 1; +} + +static char const *const output_fields[] = { + "model", + "type", + "pressure_kPa", + "temperature_C", + "code", + "mic", + NULL, +}; + +r_device const tpms_sefis_m3 = { + .name = "Sefis M3 / Careud / Sykik SRTP300 TPMS", + .modulation = FSK_PULSE_PCM, + .short_width = 52, + .long_width = 52, + .reset_limit = 5000, + .decode_fn = &tpms_sefis_m3_decode, + .fields = output_fields, +};