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.
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.
* 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>
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
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.
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)