Commit Graph

3911 Commits

Author SHA1 Message Date
Benjamin Larsson
fdfc433a16 Add ENABLE_COMPILE_LOW_MEMORY option (awk) to build Mongoose in smaller pieces
The vendored Mongoose 6.16 ships as one amalgamated src/mongoose.c
(~16k lines); compiling it with optimization needs a lot of RAM, and on
small machines the compiler can die building mongoose.c with an
out-of-memory error ("virtual memory exhausted", "cc1: out of memory").

Add an opt-in CMake option, ENABLE_COMPILE_LOW_MEMORY (default OFF),
that re-splits mongoose.c + include/mongoose.h back into their ~30
original modules at configure time via the new tools/unamalgam.sh, so
each compiles as a small translation unit with a much lower memory peak.
The default build is unchanged: it still compiles the amalgamated
mongoose.c.

tools/unamalgam.sh is a POSIX sh + awk implementation (no Python): it
needs only sh and awk, which the Linux/embedded hosts this option targets
have out of the box (e.g. via busybox). It splits on the amalgamation's
#ifdef MG_MODULE_LINES / #line markers, restores the
/* Amalgamated: #include */ lines, and applies the deterministic
adaptations rtl_433 needs to build the pieces as separate translation
units instead of one:
- an umbrella mongoose.h including the module headers in amalgamation
  order, defining MG_INTERNAL empty for cross-TU linkage
- #include "mongoose.h" prepended to each module for its feature macros
- mg_uri.c + the mg_http*.c files combined into one TU
  (mg_http_modules.c) as they share file-static state
- cross-TU prototypes, the feature-header includes before their guards,
  the SSL backends' mg_internal.h include, and the moved MSG_NOSIGNAL
The generated tree is byte-identical to the reference splitter's output
(itself validated against upstream 6.16).

src/CMakeLists.txt is purely additive: mongoose.c stays in the
maintainer_update.py-generated r_433 source list; when the option is ON,
mongoose.c is excluded from compilation (HEADER_FILE_ONLY) and the
generated modules are added via target_sources. Generated modules land
in the build tree, so the source tree and the default build are
untouched. Mongoose 6.16 is end-of-life upstream, so the input is frozen.

Verified (GCC/Linux, sh=dash): both modes build and pass the full ctest
suite (15/15, including the HTTP/WS API tests); ON mode compiles the
split modules and not mongoose.c; and GET / is byte-identical between the
two binaries. The generator runs clean under dash (strict POSIX).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 01:09:21 +02:00
Christian W. Zuckschwerdt
e811b192fe minor: Fix unistd.h with MSVC 2026-07-14 14:32:48 +02:00
Benjamin Larsson
c17d9ebd76 Document how to enable a default-disabled protocol while keeping defaults
Closes #2203. Explain the -R -N -R N technique in docs/OPERATION.md and add
an example to README.md: a negative -R as the first option loads the default
set before excluding N, so a following positive -R N adds it back on top.
2026-07-14 13:26:31 +02:00
klohner
362bb9062c Add flex decoder config for Mercator ceiling fan remote
Config reverse engineered and confirmed working by the community in
https://github.com/merbanan/rtl_433/issues/2200, never turned into a PR.
Re-verified here against the original reporter's own real captures
(button-to-file mapping confirmed from the issue body).
2026-07-14 13:02:43 +02:00
Benjamin Larsson
928817e0ef Add support for Kingspan/Watchman Plus (Niveau) oil tank monitor
Protocol reverse engineered from real captures and analysis in
https://github.com/merbanan/rtl_433/issues/2133 and the earlier
https://groups.google.com/g/rtl_433/c/VJSPl7h0848 thread, re-derived from
scratch and cross-checked against 18 real messages from 3 independent
devices (ID, level, and battery-low all confirmed; no whole-message
checksum found). RF timing (short_width/long_width) is a best estimate
from real captures in both threads, not live-verified end-to-end -- see
file header.
2026-07-14 12:20:43 +02:00
Benjamin Larsson
dd55d46960 Add support for Eberle Instat 868r1 floor heating thermostat remote
Protocol reverse engineered from real captures and analysis linked in
https://github.com/merbanan/rtl_433/issues/1951 (dottoreD/rtl_433_tests)
and cross-checked against 963 real examples (927 Learn + 36 On/Off), plus
independently confirmed action codes from crasu/gnu-heating. Modulation
timing is a best estimate (see file header) pending live hardware or a
raw capture that decodes end-to-end.
2026-07-14 01:46:07 +02:00
Benjamin Larsson
9bea552a2e Fix Honeywell decoder missing messages due to fragile Manchester demod
https://github.com/merbanan/rtl_433/issues/2015: 5816/5820L/2GIG sensors
at close range fail to decode, especially at close range where receiver
AGC overload makes the pre-message "gap" noisy. klohner traced this to
the demod itself: OOK_PULSE_MANCHESTER_ZEROBIT has no preamble-alignment
step, so noise before the real signal starts throws off its bit count
from the very first edge and corrupts the whole message, even though the
underlying pulse train is intact -- confirmed by a flex decoder using raw
PCM with an explicit preamble search catching messages the built-in
decoder missed entirely.

Switch to OOK_PULSE_PCM (half-bit width 136us, matching klohner's
measurement, not the previous 156us) and do the preamble search and
Manchester decode explicitly in decode_fn, looping over every preamble
match and validating by CRC rather than trusting the first (the
preamble's repeating pattern can yield more than one candidate
alignment).

Verified against real captures from the issue: previously-working
samples still decode identically; a sample where the old decoder
silently dropped an entire "open" event now recovers it (see the
rtl_433_tests fixture update); the exact 2019 rtl_433_tests captures
referenced in the issue go from 0/4 files decoding to 2/4 (the other two
are confirmed pure receiver saturation -- an unrelated, separate AGC
problem noted in the issue, not fixable at the decoder level).
2026-07-13 23:55:45 +02:00
Benjamin Larsson
e48537c525 Add decoder for Auriol HG04641A temperature station
https://github.com/merbanan/rtl_433/issues/2014: the protocol was fully
reverse-engineered in the issue (OOK PPM timing, checksum, 2's-complement
temperature confirmed via a freezer test, battery-low bit confirmed via
an old-vs-fresh-battery comparison, even a working Arduino sketch) but
never ported into rtl_433 before the reporter lost interest.

Verified against all of the issue's distinct examples via -y, including
a low-battery reading and a second physical unit from a neighbor's
device. One correction: the original post's "-22.6 C" label for one
capture doesn't match the checksum-verified 2's-complement decode (which
gives +22.6 C) -- that label predates the freezer test that established
the actual encoding, so it looks like an early mislabeling rather than a
real discrepancy.

The wire checksum is only 4 bits, too weak to reject noise reliably on
its own -- enabling this against the full rtl_433_tests corpus produced
two dozen false positives on unrelated weather-sensor protocols with
similar OOK/PPM timing. Added a flags-nibble check and a plausible
temperature range, both satisfied by every real capture available, which
eliminates all of them.
2026-07-13 23:12:15 +02:00
Christian W. Zuckschwerdt
0b1c699013 minor: Clean up microseconds in comments 2026-07-13 22:47:01 +02:00
Benjamin Larsson
f356702205 minor: Escape @ in tpms_jansite_ty468.c doc comment
@zuckschwerdt collided with Doxygen's @-command syntax and broke the
style-check CI job.
2026-07-13 22:34:00 +02:00
Benjamin Larsson
efc81b10c2 Accept either checksum fold for Oregon Scientific v1
https://github.com/merbanan/rtl_433/issues/1886: reported the checksum
fold as broken for some emitters, with a fix that adds 1 before masking
when the raw sum exceeds 0x180.

The standard end-around-carry fold already in place matches the
documented OSv1 algorithm and its worked examples (sum 0x1b9 -> checksum
0xba, from https://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf),
so it's very likely already correct in general -- the reporter's data
came from a distant sensor on a Yagi antenna, so the one row that only
matches the proposed alternate fold could just as easily be a weak-signal
bit error rather than a genuine protocol quirk. Lacking any capture that
resolves this cleanly, compute both folds and accept a message if either
matches, rather than picking one and risking rejecting good messages.
2026-07-13 22:30:41 +02:00
Christian W. Zuckschwerdt
e8242d61dd Refactor Fineoffset WH2 BREAKING change for Agimex Rosenborg 66796 (#3603)
Enable protocol 356 if you have a Rosenborg-66796.
2026-07-13 22:22:18 +02:00
Benjamin Larsson
596f47a54d Add decoder for Jansite TPMS TY-468-eu2 / KKMOON TPMS
https://github.com/merbanan/rtl_433/issues/2025: same SP372-chip-family
OOK/Manchester encoding as tpms_imars_t240.c (#1820, confirmed the same
by @zuckschwerdt in comment #34). Unlike #1820, this issue has real
device-display PSI/C readings paired with raw codes, which is what lets
this one be an actual verified decoder instead of a guess.

B3/B4 are still an affine function of B0, and temperature/pressure follow
the same byte-sum shape guessed for #1820: temperature_C = temp_offset -
(B2+B5)%256, pressure_kPa = (pressure_offset - (B5+B6)%256) * 2.5. Two
physical units are confirmed, each with its own checksum and offsets
(0xfb: 224/273, 0x64: 153/201), both fit against real ground truth in
the issue: 9/10 and 3/3 distinct temperatures match exactly, pressure
matches within ~0.5 PSI (manual gauge reading precision).
2026-07-13 21:55:58 +02:00
Benjamin Larsson
9851e7bdcb Add decoder for iMars T240 TPMS
https://github.com/merbanan/rtl_433/issues/1820: OOK, Manchester encoded,
50 us half-bit signal mistaken for XOR/stream-cipher encryption for years,
since only a tail-byte and a low-nibble check were ever confirmed.

B3 and B4 are each an affine function of B0 (B3 = B0 - k1, B4 = k2 - B0),
making (B3 + B4) mod 256 a fixed per-unit checksum. Two physical sensor
units are confirmed in the issue's captures, with checksums 0x41 and
0x3c; both are recognized here.

Verified against all 9 real packets in samples2.zip plus 37 codes derived
from a separate text dump in the issue, cross-checked byte-for-byte
against the community's own manual decode posted in the thread.

No temperature/pressure mapping: no capture in the issue pairs a raw code
with a numeric ground-truth reading, so only the validated raw code is
output for now.
2026-07-13 21:31:01 +02:00
Verstreubulator
430793a69c Add Fine Offset / Ecowitt WH52 soil moisture/temperature/EC decoder (#3602)
* Add Fine Offset / Ecowitt WH52 soil moisture/temperature/EC decoder
2026-07-13 20:47:30 +02:00
David Todd
a85f6e8e12 Minor changes, primarily affecting output format (#3596)
* simplify use of alternative CRC init; use hum2 rather than light
2026-07-13 20:38:32 +02:00
Benjamin Larsson
1135d3f817 Fix Wireless M-Bus long transport layer (CI=0x72) header parsing
parse_block2() only handled the CI=0x7A short header; CI=0x72 (long
header) fell through with AC/ST/CW/pl_offset left at their zero-init
value, so the data-record walker started reading records from inside
the header's own 8-byte secondary address (ID, Manufacturer, Version,
Medium) instead of the actual payload -- corrupting every decoded value.

Reported as garbage volume/flow readings (e.g. a multi-billion m3/min
volume flow, duplicate/nonsensical fields) in
https://github.com/merbanan/rtl_433/issues/1807 and
https://github.com/merbanan/rtl_433/issues/3393. Verified against 18 real
captures across both issues: AC now correctly increments per message,
and CW now correctly decodes -- which in turn correctly trips the
existing (previously dead, since CW was always 0) encryption check,
reporting "payload_encrypted" instead of decoding garbage for meters
using OMS payload encryption.
2026-07-13 19:42:38 +02:00
Benjamin Larsson
be43a5b9a4 Add flex config for Curv360 kinetic wireless light switches
OOK PWM, 28-bit fixed code per button (self-powered switch, no MCU/
pairing). Protocol found by @zuckschwerdt, refined by @merbanan, and
confirmed for real-world retransmission by @skeere in
https://github.com/merbanan/rtl_433/issues/1790.
2026-07-13 19:20:34 +02:00
Christian W. Zuckschwerdt
b683b28ada minor: Fix Schrader-SMD3MA4 code style 2026-07-13 18:22:25 +02:00
Benjamin Larsson
35fce5e440 Split Schrader SMD3MA4/NIS315G3 into two separate decoders
Per maintainer feedback, one decoder shouldn't emit multiple protocol
interpretations of the same payload under one model key:
https://github.com/merbanan/rtl_433/issues/1734#issuecomment-4957207247

schrader_SMD3MA4 and the new schrader_NIS315G3 share the same wire-format
decode (checksum, flags, id) via a common helper, differing only in
pressure scale (/5 vs /4) and model name. Both are enabled by default,
so a single physical transmission produces two output records; use -R
to select just one if that's undesirable.
2026-07-13 17:44:46 +02:00
Benjamin Larsson
255d3dff08 Add TFA Dostmann 30.3225.10 outdoor temperature sensor support
Shares the exact same 55-bit, 0xFE-preamble wire format as Fineoffset-
WH2A, causing it to be misidentified as WH2A with temperature off by a
consistent 40.0 C. Told apart by the humidity byte: real WH2A always
reports a plausible 0-100% humidity there, while this (temperature-only)
sensor always sets it to a fixed 0xFF -- confirmed against 100 real
messages plus the existing WH2A test captures, 0 counterexamples either
way.

Also adds the checksum (b[5] == sum(b[0..4])) for this new model; it
was previously an unenforced TODO in the WH2A code and verified to hold
there too, but left unenforced for WH2A itself since only 5 real WH2A
samples were available to confirm it against.

https://github.com/merbanan/rtl_433/issues/1759
2026-07-12 22:49:59 +02:00
Benjamin Larsson
3e228fa2cf Scale Eco-Eye current fields to amps, assuming centi-amp raw units
Assumes centi-amps (0.01 A/count): it is the only scale that keeps a
reading of used=2348 reported later in the issue thread (deliberately
turned on more appliances) within range of a typical CT clamp
(23.48 A); deci-amps or whole amps would put it far beyond what the
clamp/circuit could plausibly read.
2026-07-12 22:32:32 +02:00
Benjamin Larsson
96e3867ed5 Add Eco-Eye solar PV/grid current monitor decoder
FSK_PCM, 200us bit width, aa2dd4 sync word, byte-add checksum. Protocol
fully reverse-engineered by the community in
https://github.com/merbanan/rtl_433/issues/1757 (solved since 2021, but
never turned into a compiled decoder). Verified against the reporter's
real captures and cross-checked against their transmitter's own serial
console output, which matches digit for digit. Registered as protocol
351.
2026-07-12 22:12:22 +02:00
Christian W. Zuckschwerdt
1816d60d48 Add checksum, flags, and dual pressure scaling to Schrader SMD3MA4 decoder
The two low bits previously assumed to be pressure data are actually a
2-bit additive checksum (found by RonNiles), and the 3-bit flags field
maps to learn/alarm/wakeup states. Also exposes both known pressure
scalings: pressure_PSI (/5, Subaru SMD3MA4) and pressure_NIS_PSI (/4,
Nissan/Infiniti/Renault MRXNIS315G3, aka Redi-Sensor SE10001HP), since
both sensor families share the same wire protocol and cannot be told
apart from the packet alone.

Ports the fix from the maintainers' own unmerged fix-SMD3MA4 branch,
verified against real captures in
https://github.com/merbanan/rtl_433/issues/1734.
2026-07-12 21:30:51 +02:00
Ocean-85
a9cd06aade Add flex config for generic 433 MHz doorbell button
Amazon ASIN B01M1KB4UM (no brand/model name could be confirmed from
the listing). OOK PWM, 1000/3000/2000 us short/long/gap, matching the
working flex spec found by @zuckschwerdt in
https://github.com/merbanan/rtl_433/issues/1706. The device sends a
fixed, non-varying code with no visible id/button field to decompose
(only one button was ever captured), so this matches on the exact
code rather than extracting a field, following the same approach as
conf/friedlandevo.conf.

Verified against the real capture in the companion
rtl_433_tests#1706 fixture (17 repeats decoded), and confirmed it does
NOT match a second, differently-timed doorbell posted later in the
same issue thread.
2026-07-12 20:45:37 +02:00
Alex Rusnac
40744d3637 Add support for Fine Offset WN38 Black Globe Thermometer
The Ecowitt WN38 transmits the WN34 packet layout with family code 0x38
and subtype 4 (temperature scale 10, no offset, like the WN34D).
Extend the WN34 decoder to accept family code 0x38 and report
model Fineoffset-WN38. Verified against five LCD-confirmed captures
(24.5..31.0 C); test samples submitted to rtl_433_tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 20:05:55 +02:00
Benjamin Larsson
732b6282bb Check full cc ff constant pair in Govee-H5310 status frames
The status-frame disambiguator against the Govee-H5112 dual-probe
thermometer (which shares this frame's outer length and 0x71 marker)
only checked dec[9] == 0xff. Genuine H5310 status frames carry the
constant pair 0xcc 0xff at dec[8-9], so also require dec[8] == 0xcc,
strengthening the rejection of foreign frames from ~1/256 residual
ambiguity on arbitrary data to ~1/65536.

For genuine H5112 frames the separation was already sound either way
(dec[9] == 0xff would require an impossible humidity above 102% RH),
and H5112 now symmetrically rejects frames whose humidity decodes
above 100% RH -- the two accept-sets are disjoint without relying on
decoder priority ordering. Doc comment updated to describe the
two-sided separation.

Verified: both genuine H5310 status codes (rtl_433_tests#503) still
decode; H5112's own status frames still fall through to the H5112
decoder. Full local regression suite: no new false positives.
2026-07-12 19:58:51 +02:00
Paul Antaki
1ae8d45fea Add Govee-H5112 dual-probe thermometer decoder
Adapts the decoder from PR #3568 by Paul Antaki. Shares the Govee FSK
envelope reverse-engineered by Reece Neff for the H5059 (PR #3493):
sync word 2c 4c 4a, 128-byte XOR key, CRC-16/AUG-CCITT.

Both probe temperatures and humidity are packed into one 32-bit
little-endian word by the device's own firmware: 11 bits each for
probe1/probe2 (0.1 C/count, zero-anchored at -40 C) and 10 bits for
humidity (0.1%/count). Confirmed by dumping and disassembling the
H5112's FR8016HA firmware -- it contains a sensor-packing function
with this exact symmetric layout. Probe 2 is read directly as an
absolute value like probe 1: no per-device state, no wrap-count
tracking, no cold-start dependency.

Two frame types: msg_class 0x13 periodic report (~10 min interval,
full sensor data + 10-entry history buffer at ~1-minute intervals) and
msg_class 0x71 triggered status response (reply to gateway poll, same
sensor fields, no history).

Beyond the original PR, two guards make the separation from the other
Govee-family decoders structural rather than priority-dependent:
each msg_class must carry its exact documented payload length (57 or
28 bytes), and frames whose humidity decodes above 100% RH are
rejected -- the latter is the mirror of H5310's dec[9] == 0xff status
check, since a genuine H5310 status reply always decodes here as an
impossible 102.3% RH. Priority ordering (5, between H5310's 0 and
H5059's 10) remains as belt-and-braces only.

Verified against all 3 real captures from the companion
rtl_433_tests#505 PR -- two room-temperature devices (one with the
history buffer) and one fridge/freezer device (probe1 ~2.3 C, probe2
~-24.0 C) -- all match exactly. Also verified the H5310 status frames
that previously double-decoded as garbage H5112 records (102.3% RH)
under -y are now rejected by the humidity guard. Full local regression
suite: no new false positives.
2026-07-12 18:09:03 +02:00
Paul Antaki
a873c5423e Fix Govee-H5310/H5112 status-frame collision, drop ping decoding
Status-reply frames (outer_len=0x1f, marker 0x71) are ambiguous between
the Govee-H5310 pool/spa thermometer and the Govee-H5112 dual-probe
thermometer, which share the same outer envelope and happen to use the
same length/marker for their own status-reply frame. H5310 was
claiming H5112's status frames whenever they didn't happen to be
distinguishable by luck.

Fixed with a disambiguator: genuine H5310 status frames always carry
the constant pair 0xcc 0xff at dec[8-9], while H5112's dec[9] is part
of its own packed sensor word and only rarely equals 0xff by chance.
Rejecting (DECODE_ABORT_EARLY) whenever dec[9] != 0xff resolves the
collision in all but a residual ~1/256 case, letting the frame fall
through to the H5112 decoder instead.

There's no equivalent fix available for the ping frame (LL=0x1c, marker
0x70): it carries no battery/temperature data to cross-check against,
and the marker/length combination is shared with other Govee gateway
devices (confirmed on H5112) with no way to attribute it by frame
structure alone. Rather than risk misattributing another device's ping,
H5310 no longer recognizes or decodes ping frames at all.

From rtl_433#3568 (Pablo1), split out from that PR's H5112 decoder
addition. Verified against the existing regression captures
(rtl_433_tests#503): the 2 kept .cu8 samples (Temperature Update,
Periodic Update) are unaffected. The 2 previously-extracted ping codes
in codes_test.txt (rtl_433_tests, to be updated separately) no longer
decode, as expected given ping support is intentionally removed. Full
local regression suite: no new false positives.
2026-07-12 17:02:46 +02:00
Paul Antaki
7ce77c1088 Tighten Govee-H5059 msg_class boundary
An unrecognized msg_class (neither telemetry 0x11, pairing 0x01, nor
0x02) fell through to a generic "Unknown" event and was still reported
as a valid Govee-H5059 frame. This let H5059 claim frames belonging to
other devices in the same shared Govee FSK envelope (sync word, XOR
key, CRC-16) whenever their own msg_class byte didn't happen to match
one of H5059's three known values -- concretely, this caused a frame
collision against the Govee-H5310 decoder.

Now returns DECODE_ABORT_EARLY for any unrecognized msg_class instead
of emitting a data record, letting other decoders in the family (and
lower-priority passes) get a fair shot at frames H5059 doesn't actually
understand.

From rtl_433#3568 (Pablo1), split out from that PR's H5112 decoder
addition. Verified against both existing regression captures
(rtl_433_tests#503, water leak and post-alarm) -- unaffected, since
both use recognized msg_class values. Full local regression suite: no
new false positives.
2026-07-12 16:59:03 +02:00
Paul Antaki
9d96718e4c Add Govee-H5310 pool/spa thermometer decoder
Adapts the decoder from PR #3564 by Paul Antaki. The H5310 shares the
Govee FSK framing reverse-engineered by Reece Neff for the H5059 water
leak detector (PR #3493): sync word 2c 4c 4a, 128-byte XOR key, and
CRC-16/AUG-CCITT. This adds the four H5310-specific payload layouts:

- Temperature Update (LL=0x10): forced report or unit-change action
- Periodic Update (LL=0x3d): autonomous ~10-minute broadcast
- Status Reply (LL=0x1f): response to a gateway poll
- Ping (LL=0x1c): connectivity heartbeat, no temperature/battery data

Temperature formula T_C = (raw - 33168) / 10.0, confirmed against the
sensor's own display across two physical units (Pool and Spa) at
multiple temperatures. Wire encoding is always Celsius regardless of
the display unit setting.

Runs at default priority (0); H5059 runs at priority 10 so H5310 claims
shared-envelope frames first (see the companion commit for that fix).

Reviewed in the original PR by neffrw (the H5059 decoder's author).
Verified against all 7 real captures from the companion
rtl_433_tests#503 PR, across both physical units (Pool id_wire
0xf8a79cb2, Spa id_wire 0x71b09cb2) and all 4 frame types -- all match
exactly. Full local regression suite: no new false positives.
2026-07-12 16:48:21 +02:00
Paul Antaki
345bc7b134 Fix Govee-H5059 detect top-probe-only water leaks
The leak-detection condition required both dec[15] == 0x01 and
dec[17] == 0x01, which only matched a bottom-probe-only leak. A
top-probe-only leak produces dec[14]=0x01, dec[15]=0x00, dec[17]=0x01
and fell through to the generic "Telemetry" event instead of being
reported as "Water Leak".

Fixed by checking dec[17] != 0 (alarm-active) together with either
probe flag (dec[14] or dec[15]), and the specific probe is now exposed
via new leak_top/leak_bottom fields. Confirmed by neffrw (original
H5059 decoder author) in review of rtl_433#3564: "Great find on the
top probe flag since I only used the bottom probes when testing the
leak detection."

From rtl_433#3564 (Pablo1), split out from the same PR's H5310 decoder
addition.
2026-07-12 16:36:53 +02:00
Steven Bontius
c9dcd8090f Add support for Watts WFHT-RF / WFHC-MASTERH&C-RF thermostats
Adapts the decoder from PR #3561 by Steven Bontius, who reverse
engineered the protocol (documented at
https://github.com/StevenBontius/watts-wfht-ha/blob/main/docs/WATTS_WFHT_RF_PROTOCOL.md)
for a newer, LCD-display generation of Watts underfloor heating
thermostat -- unrelated to the existing watts_thermostat.c decoder,
which targets an older ADEV-based generation with a completely
different protocol (PWM vs. this one's Manchester, different frame
layout, no CRC-16).

Verified against:
- The 2 worked examples in the protocol doc (independently recomputed
  both CRC-8 and CRC-16/CMS in Python against the documented
  parameters; both check out).
- All 3 real captures from the companion rtl_433_tests#502 PR (pairing,
  on, off) -- decode correctly, including the ~2 Hz pairing cadence,
  the A-B-A setpoint jitter around a real transition, and the 0.0 C
  setpoint on the "off" capture.

Full local regression suite: no new false positives.
2026-07-12 16:17:36 +02:00
Jacob Maxa
b4b5328226 Add support for TFA Dostmann 30.390X T/H sensors series
Adapts the decoder from PR #3446 by Jacob Maxa, who reverse engineered
the protocol for 5 related sensors in TFA's ID+ series (30.3901.02,
30.3902.02, 30.3905.02, 30.3906.02, 30.3908.02) and confirmed the
CRC-32 parameters with the maintainer.

Fixes found while investigating the PR (which is still open, blocked
on unaddressed review feedback):

- The preamble/length bounds check compared the found sync offset
  against a fixed bit-length constant instead of the actual row length.
  This wrongly rejected valid short frames (30/24 bytes) unless enough
  trailing bits happened to be present for the longest 36 byte variant,
  and never actually verified the full frame fit in the row before
  reading it. Now reads the length byte first and validates against
  the row length directly.
- The "model" data line used a runtime-built string; project convention
  (enforced by tests/symbolizer.py) requires a fixed string per model.
  ID-A0/ID-A6 share an identical frame layout, so they're merged into
  one switch case reporting both "model" lines guarded by DATA_COND
  (the same pattern used throughout fineoffset.c, oregon_scientific.c,
  and ~17 other decoders for same-layout model variants); ID-A3/A4/A5
  have genuinely different field sets, so each keeps its own data_make()
  call.
- Humidity lost its decimal digit through a plain int cast; kept as a
  scaled double like temperature.
- A stray nibble of the status byte had leaked into the printed id
  string; now a clean 4-byte/8-hex-char id.
- crc32_reveng() wasn't static, and used a #include "logger.h" +
  print_logf() instead of decoder_log().

No raw .cu8 captures were available to verify this decoder against
(checked both this repo and rtl_433_tests), but the PR's own doc-comment
included 6 worked examples (hand-transcribed hex for ID-A0/A3/A5 and
three sequential ID-A4 readings) that all validate against the CRC-32
above -- real device data, not fabricated. The 3 ID-A4 frames are also
internally consistent as a sliding window (each one's "current" reading
matches the next frame's "1 reading ago" value), further confirming the
field layout. Verified all 6 decode correctly, plus CRC-failure and
no-preamble rejection. Full local regression suite: no new false
positives.
2026-07-12 15:14:35 +02:00
Steven Walter
1b6a70123a Add support for EN2058 four probe temperature sensor
Adds the decoder from PR #3498, with fixes found by testing against
the real captures in rtl_433_tests#400 (all 6 captures decode exactly
matching their physical readouts):

- Fixes the 24-bit device ID: it was raw-pointer-cast into a native
  uint32_t, which byte-reverses it on little-endian platforms (the
  vast majority of rtl_433 users). Now extracted into a 3-byte buffer
  and combined manually, matching how the temperature fields (which
  already used ntohs() correctly) avoid the same class of bug.
- Fixes the dead "preamble not found" check: bitbuffer_search()
  returns unsigned, so `if (offset < 0)` could never be true. Now
  compares against the actual sentinel (bits_per_row[0]).
- Changes "mic" from a raw DATA_INT checksum byte to the standard
  DATA_STRING "CHECKSUM", matching every other decoder's convention.
- Fixes the checksum formula (0x56 + id bytes + sum of the 8
  temperature bytes) & 0xff, confirmed against all 6 real captures.
  Enabled by default now that it's verified reliable.
- Adds the previously-undecoded 8 bit sequence counter (sent as a
  duplicated byte, increments by 2 each repeat), gated on the row
  having enough bits so short/synthetic frames still decode.
- Uses add_bytes() and decoder_log() on checksum failure, matching
  sibling decoders (esun_en2053, ambientweather_wh31e).
- double -> float for the temperature accumulators.

Verified against all 6 real captures in rtl_433_tests#400 and the
full local regression suite (no new false positives).
2026-07-12 12:15:01 +02:00
avicarmeli
c55f12c803 Add checksum, correction, and overrides to Arad/Master Meter Dialog3G
Rewrites the decoder (previously disabled, no checksum) with a real
MIC: a 40-bit LFSR digest over the 11 payload bytes, correcting up to
3 flipped bits via syndrome matching, so it can be enabled by default.
Shortens the sync match to 4 bytes for better sensitivity, and adds
optional decoder args for serial filtering and gear/unit override when
auto-detection isn't reliable.

Addresses the remaining open review feedback from PR #3489:
- Fixes create_fn to take char const *args (matching r_device's
  actual signature) instead of mutating the caller's string in place;
  only the strdup'd copy is ever modified now.
- Adds a separate "corrections" field (0-3) and reports "mic" as
  plain "CHECKSUM" always, instead of overloading "mic" with
  "CHECKSUM-CORR" as a proxy for the correction count.
- Exposes the resolved gear/scale factor as its own "gear" field,
  whether auto-detected or user-overridden.

Verified against the two real example frames from the PR thread
(consistent UID/serial, plausible small volume increase between
readings), a hand-flipped single-bit-error frame (correctly repaired,
corrections: 1, identical output to the clean frame), and a 4-bit-error
frame beyond the correction limit (correctly rejected, no false
decode). Full local regression suite passes with no new false
positives despite the shorter sync and default-enabled decoder.

Co-authored-by: Bruno OCTAU <nice.burger@free.fr>
2026-07-12 10:31:29 +02:00
Ryan Romanchuk
720bbfa7f5 Add support for Ecowitt WN20 rain gauge
Extends the WH31E/WH40/WS68 dispatcher with message type 0x20 for the
WN20 (10-byte payload, one extra unknown byte before CRC/SUM vs WH40's
9, full-byte battery encoding at 0.02V/unit).

Addresses zuckschwerdt's review on PR #3467: fixes the misaligned
variable declarations, makes battery_ok a proper 0/1 boolean instead
of a 0.0-1.0 fraction, adds a separate battery_pct field, and documents
why the "data" extra field spans 5 bytes past WN20's own 10-byte
payload (the shared 18-byte extraction buffer used by every message
type in this dispatcher).

Verified against the 3 real captures from the linked rtl_433_tests#487
(id 11399, 1.9mm rain, 3.04V/100% battery, matching the GW1100B
gateway's own readings).
2026-07-12 00:28:12 +02:00
Christian W. Zuckschwerdt
7fbc40bcb0 Add support for WS69 pressure sensor 2026-07-12 00:14:06 +02:00
Giorgi Kotchlamazashvili
40bef13e2b Add flex configs for JCHENG SECURITY Contact and PIR sensors
Converts the native decoder proposed in PR #3448 into flex configs
per zuckschwerdt's review comment on that PR: no checksum, no
advanced decoding, so a flex config fits better than a full decoder.

No real captures were available for either sensor (the PIR test
recordings were never available, and the promised Contact recordings
were never attached), so this is verified only against synthetic
codes derived directly from the PR's own documented bit layout.
2026-07-12 00:00:34 +02:00
David Woodhouse
2fa6593778 Add decoder for FSL Cricket Scoreboard Controller
FSK PCM with Manchester-encoded data, 10x repeated 72-bit blocks each
carrying a field ID and a 3-digit value validated by fixed position
markers. Cleans up brace/indent style and removes a dead assignment
from the original PR (merbanan/rtl_433#3443).

Verified against 18 real captures across three recording sessions
(field IDs 3, 5, 8, 14), all matching the ground truth documented in
the linked test-data repo's commit history (values stepping by 10 up
to 50).
2026-07-11 23:19:12 +02:00
Markus Dauberschmidt
dc1627bd73 Add decoder for Dickert MAHS433-01 garage door remote control
Reworks PR #3321 (closes #2983): fixes a buffer overflow where the
10-character dipswitch string had no room for its null terminator and
bled into the adjacent factory-code buffer on every real packet, changes
id to DATA_INT per review feedback, renames dipswitches/facswitches to
dipswitch/facswitch to match the existing convention in cardin.c, and
trims the bit extraction to the 36 valid bits instead of reading 1 bit
past the row length.

Verified against 6 real captures and all 12 BitBench codes from the
issue thread.
2026-07-11 22:25:01 +02:00
Benjamin Larsson
f2c7c0bbf9 Add support for EMOS E6018 as a variant of the E6016 decoder (closes #1692)
The E6018 uses the exact same 120-bit frame and checksum as the E6016
(protocol 214), just without a wind sensor or DCF77 radio clock: it
always reports 0 wind and a fixed fake clock (2015-01-01, only
time-of-day advances). Distinguished by byte 4's top 2 bits (post
invert): 0 for E6018, 2 for E6016 (only these two values observed).
When 0, report model EMOS-E6018 and suppress the wind/radio_clock
fields instead of emitting the fake/zero values.

Verified against all 13 codes from the issue: 5 real E6018 readings
plus 3 real E6016 readings (2 different units) and the decoder's own
pre-existing example, all with valid checksums.
2026-07-11 20:59:23 +02:00
ProfBoc75
b50f724d1c Add support for SmarTire TPMS sensor, Vantage/Aston Martin DB9 protocol
Decoder for SmarTire TPMS sensors used on Aston Martin Vantage/DB9
vehicles (2005-2011), using OOK PCM framing with differential Manchester
coding and CRC-7 checksum validation.

Ported from merbanan/rtl_433#3072 (originally by Bruno OCTAU/ProfBoc75),
closes #3067.
2026-07-11 20:31:13 +02:00
Benjamin Larsson
200ac42cd2 Add support for Vivint Door/Window Sensors (345 MHz)
Decoder for the Vivint V-DW21R-345 door/window sensor, closely related
to the Honeywell/2GIG (Ademco) 345 MHz protocol: OOK Manchester with a
0xFFFE preamble and CRC-16 based integrity check (packed 12-bit CRC for
0x7a/0x79/0x74 event frames, standard 16-bit CRC for 0xd0 power-on
frames).

Incorporates the full issue thread, including the 2026 follow-up
discussion which corrected and extended the original 2020 analysis:

- The 32 bit id decodes to the sensor's printed TXID label by splitting
  it into a 12 bit and a 20 bit decimal number (confirmed against 3
  independent sensors' labels).
- The counter is bytes 1-2 (not 2-3 as first guessed), confirmed against
  a ~14000 message capture with a steadily incrementing count.
- The open/closed door state could not be determined: a bit that matched
  the small original 2020 sample by coincidence fails on ~35% of the
  larger labeled capture, and nobody in the thread found a working
  formula, so it is intentionally not exposed.
- Added the 0x74 "heartbeat" event type alongside 0x7a/0x79.

Verified against all 4 raw captures plus 88 payloads transcribed from the
issue thread (all decode with valid CRC).

Closes #1504.
2026-07-11 19:13:19 +02:00
Benjamin Larsson
87bab9fa07 Add support for LaCrosse WS6262 to Emax decoder 2026-07-11 18:10:30 +02:00
Benjamin Larsson
93a8f7aef3 Fix Emax X7064A checksum offset wrongly applied to Rain/Wind frames 2026-07-11 18:10:30 +02:00
Benjamin Larsson
0ecab710d8 Add flex configs for JNStar driveway beam and Hosmart HS002 PIR
Covers issue #1689. JNStar/JNWR sends a fixed 25-bit code (OOK PWM);
matched directly since only one unit was tested and no id/data split
could be confirmed. Hosmart HS002 (also sold as eMacros) protocol
reverse engineered by @sbalabanov: FSK PWM inverted 25-bit message with
id/model_id/channel/battery_ok fields.
2026-07-11 17:54:36 +02:00
Cole Munz
11895fe3bf Fix uint8_t offset wraparound in m_bus parse_payload (#3597) 2026-07-11 13:10:30 +02:00
Benjamin Larsson
93b613e51e Add OWL CM130 energy monitor support to oregon_scientific (closes #1493)
The CM130 is an Oregon-Scientific v3 family energy monitor, sister to the
CM160/CM180. Decode the 96-bit message (preamble 00 00 00 60): house
code, instantaneous power (16-bit LE count, 16 W per count) and a
cumulative energy counter reported as energy_kWh = counter / 8192
(8192 = 2^13 = 16 * 512, matching the power scale of 16). This is the
meter's absolute lifetime total, so it reads higher than the console,
which shows a relative value with a device-specific offset.

The checksum, unsolved in the issue for years, is a CRC-8 (poly 0x07,
init 0) over message bytes 1..10 compared against the nibble-swapped
byte 11; validated against all 50 known messages.
2026-07-11 11:41:40 +02:00
Benjamin Larsson
d9efb4207d Add support for Esun EN2053 two-channel BBQ thermometer (closes #1478)
Generic-brand two-probe BBQ/meat thermometer, FCC ID 2APN2-EN2053,
sold e.g. as Feelle Digital Thermometer. Reverse engineered from the
captures, audio recording and annotated codes posted in the issue.

The checksum byte, unsolved in the issue thread, packs four even-parity
flags and a modulo-8 sum of the four message bytes; it matches all 37
known-good messages across both probe channels.
2026-07-11 00:22:11 +02:00