4042 Commits

Author SHA1 Message Date
Northern Man
15ac67bc9e Update Vivint decoder to include DW11 inputs (#3637)
Decode more fields.
2026-07-29 15:43:10 +02:00
Mattias Jonsson
308fd14e61 Add water usage to Bresser-Garden, fix truncated 16-bit fields
Follow-up to the initial Bresser SmartHome Garden decoder.

The Water Timer meters the water it delivers. Emit that as water_usage_l
from the 0x04 watering event, and from the 0x02 status report and 0xa1
run response, which both carry the last completed run's figure. The unit
is 0.1 L, checked against the app's own display and a measured volume.
The meter is rated 5-35 L/min and reads true within that band but
under-reads below it, which the doc-comment records so the number is not
taken as exact at drip flow rates.

Three little-endian fields were read one byte wide, silently truncating
any value above 255: the 0x04 run duration (a 360 s run reported 104),
the 0x21 run-command duration, and the water usage itself (a 41.0 L run
reported 15.4 L).

Decode more of the protocol: soil_rssi in the 0x0a relay, the trigger's
third source (the button on the Water Timer itself), and the 0x86 water
limit, which the valve enforces on its own. Give every frame a msg_name
and a full msg_type so consumers can tell frames apart, and report each
frame under the model of its actual transmitter.

Correct the direct-mode description: with "Relay Communication" off the
soil sensor talks straight to the gateway with 0x03, on a different
frequency, so it is not the never-emitted type the header claimed. The
sensor also receives - pairing, config and acks all reach it.

Harden the decode: reject msg_length above 20, since the payload cannot
run past b[30] with the CRC in the last two of the fixed 33 bytes;
require exactly one row; fix the 0x86 plan count to (msg_length - 1) / 7
for the 1-byte header, accepting only the two valid lengths; cast the id
bytes before shifting into the sign bit; and read the soil temperature
as signed. Replace eight copies of the raw-payload hex loop with one
bounded helper.

Rework the doc-comment into a protocol spec: frame envelope, per-message
payload layouts, per-channel frequencies, and an explicit list of what
remains undecoded.
2026-07-27 20:35:16 +02:00
Cole Munz
3e8c23338a Reject out-of-range WT450 temperature and humidity
Only 2 parity bits gate this frame, so roughly 1 in 4 random trailing
bytes pass for any given data. Humidity is a raw 7 bit field with no
ceiling at 100 and temperature is a full 8 bit field offset by -50, so
noise that matches the preamble and passes parity decodes as 127%
humidity or 205.0 C. gt_wt_02 and gt_wt_03 already guard this.

Reject humidity over 100% and temperature outside -35 to 75 C; the
WT450H is documented -30 to 70 C, with a little headroom.
2026-07-27 20:33:00 +02:00
Cole Munz
65e26f5180 Fix [GT-WT03] reject out-of-range temperature and humidity (#3632)
* Fix [GT-WT03] reject out-of-range temperature and humidity (closes #3630)

The decoder had no temperature sanity check, so a noise packet that
happens to pass the checksum can be reported as a real reading (seen
as temperature_C: -177.3 in the wild). Humidity was also only clamped
to 0/100 for any out-of-range byte instead of validated, so garbage
values could slip through as plausible-looking readings.

Add a range check for temperature (-50.0 C to 70.0 C, with headroom
for the sensor's own -50.1/70.1 Lo/Hi indicator readings) and switch
humidity to the same exact LL/HH sentinel check gt_wt_02 already uses,
instead of clamping every out-of-range byte.

Co-authored-by: munzzyy <Munzzyy1@proton.me>
2026-07-24 11:43:43 -04:00
Greg Troxel
9226f12998 {,examples/}README.md: Link to wiki page on other software (#3631)
* examples/README.md: Link to wiki page on other software
* README: link to wiki page of related software
2026-07-24 09:19:14 -04:00
Mattias Jonsson
0ab15c7f88 Fully decode the Bresser SmartHome Garden protocol
Decode soil-moisture telemetry (0x09), the water-timer relay (0x0a) and
watering events (0x04), base config/schedule (0x85/0x86/0x20), INIT/pairing
(0x01), the Water Timer's status/config/schedule polls (0x02/0x05/0x06) and
the gateway's replies (reply type = request|0x80, echoing the counter),
heartbeat/beacon and acknowledgements. Each frame is reported under its
transmitter's model from the device-class byte of the 32-bit id, corroborated
by transmit frequency.

Polling is autonomous rather than gateway-gated: with the gateway powered off
the Water Timer keeps sending 0x02 on a fixed ~9.4 min timer and simply gets no
reply, while 0x05/0x06 never fire, so those look event-driven instead.

Reverse-engineered with battery-pull, schedule-split, base-power-off,
gateway-power-off and poll-pairing analysis; fixtures added in rtl_433_tests.
2026-07-22 22:49:45 +02:00
ProfBoc75
f47b0b7a71 Add Bresser SmartHome Garden decoder for soil moisture and water timer
Initial FSK PCM decoder for the Bresser SmartHome Garden 433 MHz irrigation
set (Fujian Baldr / Homgar / RainPoint family): soil moisture sensor and
water timer.
2026-07-22 22:49:45 +02:00
Benjamin Larsson
4ff81dd03a Harden FS20/FHT and disable it by default
Command codes 0x1c-0x1f are documented as unused ("frei") and never
sent by real devices, so accepting them only widened the false-decode
surface for foreign bitstreams that happen to pass parity and the
checksum band.
2026-07-22 03:03:39 +02:00
Benjamin Larsson
30783dae84 Require exact repeat for Oregon-SL109H's weak 4-bit checksum
num_rows>=2 only checked that some other row existed, not that it
agreed with the candidate row, so any unrelated garbage row let a
lone checksum-passing frame through. Require the 38-bit row to repeat
identically via bitbuffer_find_repeated_row().

Costs 4 of 104 real captures a redundant decode where the device's
other repeat was itself corrupted in that capture; accepted trade-off.
2026-07-22 02:26:47 +02:00
Benjamin Larsson
c87b88aafd Harden Oregon-CM130 preamble check against short-anchor collisions
The search pattern only required one zero byte before the 0x60 sync
byte, not the three documented in the file's own comment ("CM130
preamble is 00 00 00 60") and confirmed present in all 3 real captures
in rtl_433_tests. A frame with just one zero byte, a 0x60, and a
matching CRC-8 decoded successfully -- gated on nothing stronger than
the same ~1-in-256 checksum odds every other check here also relies on.

Matching the full 3-byte preamble quadruples the anchor from 16 to 32
bits with no behavior change for real signals (verified against all 3
captures and all 47 codes_test.txt readings).
2026-07-22 01:19:27 +02:00
Benjamin Larsson
9e0fde2c71 Fix no-op preamble check in GM-Aftermarket TPMS decoder
bitbuffer_search()'s "not found" result is unsigned and never < 0, and
the extraction right after it ignored the search position anyway --
the preamble check was a silent no-op, gating decode on nothing but
the weak 8 bit checksum (~1-in-256 odds). Confirmed as a real
false-positive source in issue #3611 (@zuckschwerdt, @merbanan).

Frame length is already pinned to 130 bits, so the preamble is always
at a fixed offset; check it directly on the extracted bytes instead.
2026-07-22 01:07:01 +02:00
Benjamin Larsson
aaa085a8e6 Fix Wireless M-Bus parsing garbage values for unrecognized CI codes
m_bus_decode_format_b() never called parse_block2(), so Mode C Format B
telegrams never got CI/AC/ST/CW populated and silently defaulted CW to
"not encrypted". Worse, the byte being called "CI" (0x8C-0x8F) wasn't
an Application Layer CI at all -- per EN 13757-4 (confirmed against
wmbusmeters) it's the Extended Link Layer, an optional sub-header for
addressing/encryption between the DLL and the real TPL. Both bugs let
parse_payload() run on data it had no business reading, producing
nonsense values (temperatures in the quintillions, etc.) while still
reporting a valid CRC.

m_bus_parse_ci() now unwraps ELL (variants I-IV, fixed lengths 2/8/10/16
bytes, security mode from the Session Number) and AFL (0x90, EN
13757-7, skippable via its own length byte) to reach the real TPL CI,
and added the "no header" TPL format (CI 0x78, previously only handled
for its Qundis-specific sub-case). Unrecognized/encrypted layers now
report an honest "payload_encrypted" or raw hex instead of fabricating
readings. KNX-RF is exempt (separate path, unaffected).

Verified against real captures: KAW/KAM meters genuinely AES-CTR
encrypted at the ELL layer (no key management here to decrypt), a DME
meter's ELL/AFL unwraps to a manufacturer-specific TPL CI with no
public spec, and a KAM meter's CI 0x78 payload now decodes correctly.

Regenerated 12 existing fixtures that had the fabricated values baked
into their expected output.
2026-07-21 23:56:02 +02:00
Benjamin Larsson
d9986c7c6c Harden GT-WT02 frame validation 2026-07-20 23:46:01 +02:00
Benjamin Larsson
21123be1f5 Harden Oregon SL109H frame validation 2026-07-20 23:33:44 +02:00
Benjamin Larsson
d3c9b9db95 Harden Thermor A6N frame validation 2026-07-20 22:52:46 +02:00
Christian W. Zuckschwerdt
816b0b88f3 minor: Fix printing of overlong registered protocols list 2026-07-20 20:45:45 +02:00
Benjamin Larsson
d1c3d5cc31 Fix Oil Standard collision with Smart frames 2026-07-20 20:33:48 +02:00
Benjamin Larsson
0206016a88 Add Silver Spring Networks mesh decoder
Closes #3583.
2026-07-20 19:49:53 +02:00
Benjamin Larsson
2e3a387023 Harden FS20/FHT frame validation 2026-07-19 22:26:23 +02:00
Benjamin Larsson
c3df62312d Validate the fixed trailing bit in UniFan-24V frames
The 33 bit frame's own doc comment documents a trailing bit that's
always 1, but it was never actually read or checked -- only the first
32 bits (b[0..3]) were used. Combined with the checksum being just a
weak 4-bit XOR fold (~1-in-16 odds), this made the decoder prone to
false positives against other decoders' repeated-row data (see
rtl_433_tests issue with Geevon-TX191/Cotech-367900 codes_test.txt
fixtures). Checking the documented bit halves the odds of an
accidental match.
2026-07-19 22:25:21 +02:00
Benjamin Larsson
de1ee35e8e Add support for Cotech 36-7900 rain gauge
Reverse-engineered in issue #3537: OOK PPM, 60 bit frame, no computed
checksum found. Byte layout confirmed against a full day's real
capture data -- temperature (12 bit signed, scale 10) tracks a smooth
diurnal curve from 14.7 to 23.6 C, and the rain field is a monotonic
counter that jumped by ~39-42 per confirmed bucket tip in a controlled
test and stayed flat otherwise. The mm-per-count scale wasn't
established (no water-volume calibration reported), so the rain count
is exposed raw rather than guessed at.

Bit-level analysis across all 66 unique real codes seen showed a 24
bit span that's exactly zero in every one -- likely reserved bits
rather than a coincidence, but functionally just as good as a checksum
for rejecting noise, so it's checked as one. This also narrowed the
rain field from an assumed 36 bits down to the 12 that actually vary.

Disabled by default since the reserved-bits check is a plausibility
gate, not a real computed checksum.

Closes #3537.
2026-07-19 22:25:21 +02:00
Benjamin Larsson
c348b95d19 Tighten Honda TRW TPMS decoder matching 2026-07-19 21:19:19 +02:00
Benjamin Larsson
b9d5b6a13a Add support for Honda (TRW PPA-GF33) TPMS
FSK, IEEE 802.3 Manchester coded, distinguished from Chrysler-fitment
TRW sensors (tpms_trw.c) by a different desync marker and byte layout.
Reverse-engineered in issue #3447: byte layout confirmed via CRC-8/SMBUS
validation across all captures, sensor ID confirmed against both the
value printed on the physical sensor and a commercial TPMS tool reading,
and the pressure/temperature formulas fit a 7-point pressure sweep from
the same tool (one very-low-pressure point didn't fit, noted as an
open question in the doc comment rather than guessed at).

Closes #3447.
2026-07-19 21:00:54 +02:00
Benjamin Larsson
26788f1662 Emit Govee device IDs as hex strings 2026-07-19 18:36:52 +02:00
Benjamin Larsson
c43529533a Fix Honeywell CM921 debug field labels 2026-07-19 18:36:52 +02:00
Benjamin Larsson
b38e7e130c Add Jeep TPMS decoder for the doubled-pressure Continental variant
2021 Jeep Grand Cherokee (WK2) sensors, reported as Continental by an
OBD-II scanner, transmit the exact same wire format as Citroen TPMS
(protocol 82) but with pressure at twice Citroen's raw * 1.364 scale.
Confirmed against a session where all 4 tires were set to 33-36 PSI:
the doubled formula lands in that range, the existing one reports
exactly half.

Registered as a new, disabled-by-default protocol (380) rather than
changing Citroen's formula, since the two are not reliably
distinguishable on the wire and changing Citroen would break existing
users. Both decoders fire on the same data; select the one that
matches your vehicle with -R.

Closes #3364.
2026-07-19 18:09:57 +02:00
Benjamin Larsson
27e548ab63 Fix RADIAN symbolizer decode link 2026-07-19 16:31:19 +02:00
Benjamin Larsson
cd62f26a98 Fix 2GIG KEY2E symbolizer model name 2026-07-19 16:31:11 +02:00
Benjamin Larsson
f16d84b1dc Add Elster-PowerMeter2 decoding of msg 0x7f neighbour tables (issue #3618)
Message class 0x7f is a second cleartext neighbour-table variant next
to the existing 0x57 one, differing only in where its 20-byte records
start (one byte earlier). Confirmed against real captures of both
classes.

Reported-by: ther3zz
2026-07-19 13:28:33 +02:00
Christian W. Zuckschwerdt
263ac3e867 Fix automatic discriminator selection for file input (#3554) 2026-07-19 13:23:37 +02:00
Benjamin Larsson
335d47e977 Add per-device seed decoding for Vivint contact state (issue #1504)
The door open/closed bit is XORed with a per-device keystream: a 16
bit seed burned in at manufacturing, expanded by a Rabbit stream
cipher core (RFC 4503) keyed on the transmission counter. This decoder
cannot recover the seed itself; it must be obtained externally from
several frames at known low counters. Add a create_fn accepting a
per-TXID seed table to decrypt the state when the seed is already
known:

    rtl_433 -R 342:0019-0507610=05c9,0019-0507743=dda9

Without a matching seed, state/contact_open are omitted and the raw
payload is reported in data instead.

Verified byte-exact against all 15 real open/closed-labeled frames
across 2 sensors (seeds 0x05c9 and 0xdda9) from the issue thread.
Backward compatible: unchanged when no seed is configured.
2026-07-19 12:46:17 +02:00
Benjamin Larsson
3573506b85 Improve Efergy-e2CT to reject unaligned/wrong-length frames instead of shift-searching
The bit-alignment search shifted up to ~16 times looking for a 4-bit
match (top nibble 0x0 or 0xf), giving noise many chances to accidentally
align before the 8-bit checksum was the only remaining gate. All known
real captures (15 files, one physical unit) and both documented test
codes are always already aligned on the first check, so drop the search
entirely and reject anything that isn't already aligned. Also tighten
the accepted length from 64-80 bits down to 64-65 (the 80-bit upper
bound was only ever needed for the old search), and reject
bitbuffer->num_rows != 1. Verified byte-for-byte identical output on
all 15 real captures and both documented test codes.
2026-07-19 03:37:50 +02:00
Benjamin Larsson
e49d6a8873 Disable Markisol by default (issue #3611)
Weak validation (8-bit additive checksum, no channel/control range
checks, no repeat requirement) makes it a top false-positive offender
in real-world use per issue #3611. Still selectable via -R.
2026-07-19 03:26:10 +02:00
Benjamin Larsson
83a8d0e948 Improve Schrader-MRXBC5A4 false-positive rejection (issue #3611)
The 16-bit wake+sync+start header was assumed rather than checked, so
any 61-bit noise burst that happened to pass the weak 2-bit parity
check (~1/4 of random payloads) would decode. All 4 real captures
share the identical 16-bit prefix 0111111111111111, so verify it via
bitbuffer_search() instead. Also reject bitbuffer->num_rows != 1, and
tighten the temperature sanity bound from the sensor chip's own
-40/+125 C survival rating down to 85 C, a realistic tire temperature.

Verified byte-for-byte identical output on all 4 real captures.
2026-07-19 03:15:21 +02:00
Benjamin Larsson
3631487740 Change Springfield-Soil to require 3 repeated rows
Replaces the per-row loop and consecutive-duplicate skip with
bitbuffer_find_repeated_row(3, 36): only decode a row that's repeated
at least 3 times, rejecting one-off noise instead of trying every row.
Verified against all 4 real captures, byte-for-byte identical output.
2026-07-19 03:00:29 +02:00
Benjamin Larsson
9b28ff5ca2 Fix RADIAN mic field ordering to be last
parse_payload() appends fields after data_make() runs, so mic ended up
in the middle of the record instead of after everything else. Moved it
to a trailing data_str() call after parse_payload(), matching how the
wireless M-Bus output already does it.
2026-07-19 02:52:57 +02:00
Benjamin Larsson
8f41434a17 Change m_bus_decode_val to decode the 32-bit float DIF coding
Was returning -1 (abort) unconditionally; the wire length is fixed at
4 bytes regardless of whether the value is understood, so this alone
was cutting the rest of the record stream off after the first such
field. Decodes the raw IEEE-754 bits and rounds to the nearest integer,
same convention as every other coding here (the caller's VIF-derived
exponent scales it from there).

Confirmed on RADIAN's #3408 sample: 19 H.C.A. units records, two
temperatures, two dates, and an energy field now decode past what used
to be a hard stop.
2026-07-19 02:37:23 +02:00
Benjamin Larsson
5251e4decf Change RADIAN to decode its wired M-Bus body via m_bus_parse_ci()
The body wraps a complete wired M-Bus (EN 13757-2) telegram: found by
its self-verifying length/checksum/stop-byte markers, then decoded
with the same CI/AC/ST/CW and DIF/DIFE/VIF/VIFE parsing wireless M-Bus
uses (m_bus_parse_ci(), parse_payload()). Confirmed on the #3408
sample: software_version, a timedate, and an H.C.A. units record
decode correctly before the shared parser hits an unimplemented
32-bit-float DIF coding and stops (a pre-existing limitation, not
RADIAN-specific).
2026-07-19 02:15:33 +02:00
Benjamin Larsson
dd7ac204af Add RADIAN/RADIAN0 meter transport decoder (issue #3408)
FSK_PCM at 2400 baud, UART 8N2 framing, CRC-16/KERMIT trailer. Outputs
the verified transport fields (length, control, sender/receiver ID,
CRC) and raw body bytes only; the response body looks M-Bus-like but
isn't decoded yet.

Lives in m_bus.c rather than its own file: the body is a wired M-Bus
telegram (see next commit), so this reuses the wM-Bus DIF/VIF decoder
already there instead of duplicating it. Registered as protocol 379.
2026-07-19 02:15:24 +02:00
Benjamin Larsson
a7d274924f Improve m_bus: extract m_bus_parse_ci() from parse_block2()
Pure extraction, no behavior change: the CI/AC/ST/CW parsing now takes
a pointer to the CI byte plus a caller-supplied base offset, instead of
assuming the wireless M-Bus block1 layout. Needed so other transports
that wrap a plain CI frame at a different offset can reuse it.
2026-07-19 01:59:39 +02:00
Benjamin Larsson
0d3cdea11e Fix Ford-CarRemote revert reset_limit/dedupe, keep disabled
Per review on 23fbfead (github.com/merbanan/rtl_433/commit/23fbfead#r192930622,
zuckschwerdt): raising reset_limit to bundle repeats relied on the DMC
slicer seeing a long enough trailing gap to flush the packet, which
isn't guaranteed, so real captures could silently produce no output at
all. Reverted to reporting each repeat row directly. Kept the decoder
disabled by default.
2026-07-19 01:36:55 +02:00
Benjamin Larsson
c3ff979fc1 Improve Watts-WFHTRF check preamble at a fixed offset, reject long frames
The preamble always starts right after the sync gap already stripped
by the pulse slicer, so searching for it and looping over rows was
unnecessary. Also reject frames longer than the fixed message length.
2026-07-19 01:36:55 +02:00
Benjamin Larsson
5a4d4880d3 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.
2026-07-19 01:27:34 +02:00
Christian W. Zuckschwerdt
2d029fbf36 Refactor to add proper package flush (#3619) 2026-07-18 23:55:03 +02:00
Benjamin Larsson
23fbfead7b fordremote: dedupe repeats via bitbuffer_find_repeated_row, disable
Each button press was reported once per repeat (typically 4x) because
reset_limit (4000 us) was far shorter than the real packet gap between
repeats (~52000 us, per the file's own preamble-gap comment), so every
repeat arrived as its own separate bitbuffer/callback instead of one
bitbuffer containing all of them. Raised reset_limit to bundle all
repeats into a single call, then use bitbuffer_find_repeated_row (2
repeats min) to pick one representative row and report once.

Disabled by default: doesn't decrypt the rolling code and the id/code
field semantics are unconfirmed (per the file's own doc comment).
2026-07-18 20:04:02 +02:00
Benjamin Larsson
4e127c7d30 Add Typhur Sync Gold meat thermometer probe decoder (closes #3138)
FSK_PCM at 12.5 us/bit, CRC-16 (poly 0x8005, init 0x0000). Ported and
independently re-verified from an unmerged fork (hakong/rtl_433,
feat-typhur branch) referenced in the issue: re-derived the sync word
and payload location via raw bit search (not trusting the fork's own
CRC self-check), confirmed CRC valid on all 19 real captures across
two physical probes, and confirmed the decoded values are physically
sane -- T1-T5 and ambient temperatures match the reporter's own noted
ranges, battery voltage is stable per probe, and the counter
increments monotonically per probe ID.
2026-07-18 19:44:02 +02:00
Benjamin Larsson
7c765eeff2 oria_wa150km: add checksum validation (issue #3136)
An Oregon Scientific v3 style nibble checksum, not the 16-bit XOR
previously claimed (and found not to hold) in the issue thread.
Computed on the Manchester-decoded bytes before reflect_bytes(): each
of nibbles 7-23 individually 4-bit-reflected, sum of the first 15
(mod 256) compared against the last two combined into a byte.

Verified against 39 independently-decoded real frames from two
attachments in the issue thread (26 distinct readings): matches on
all 39, and every single-bit corruption within the checked range (but
not outside it) breaks the match. Adds a "mic" output field.
2026-07-18 19:21:22 +02:00
Christian W. Zuckschwerdt
e6cb4cbf80 minor: Update symbolizer output 2026-07-18 19:19:49 +02:00
Benjamin Larsson
d4df8fcc73 Revert "Extend FSK modulation to the full set of OOK pulse-slicer codings (closes #3409)"
This reverts commit 3d75051e1d.
2026-07-18 18:21:52 +02:00
Benjamin Larsson
6745b00d8a Split Elster type-2 model name to Elster-PowerMeter2
zuckschwerdt flagged that the symbolizer/checker requires each model
name to map to a single decoder; elster_power_meter and
elster_power_meter2 shared "Elster-PowerMeter", so the type-1 decoder's
model key was getting silently overwritten. merbanan: "Lets add a v2 to
the model name." (issue #3618)
2026-07-18 18:16:53 +02:00