#3694·rtl_433

Have I solved the Watchman Advanced issue?

Author: jtopperCreated Sep 11, 2026Updated Sep 18, 2026

Like the authors of #3604 and other issues, I have a Watchman Advanced oil level monitor which I've previously struggled to get working with rtl_433 (ultimately I want to get this data into Home Assistant graphs).

Today I burned some time and Claude tokens on trying to debug the issue. I believe I've found a fix, however I don't trust the AI tool sufficiently to provide you with a PR and call it done - I'm not a radio engineer, nor a C programmer of any merit, just a guy with a Claude subscription and some time on his hands (ugh, the worst).

Other more capable contributors, given the below information, might be able to offer an alternative, better solution.

This issue is written in artisanal human prose, except where I quote the AI tool below. The patch I link to is all the robot's doing.

Approach

I captured some sample data with the rtl_433 binary by pulling and replacing the Watchman battery, then placing the device in pairing mode with its receiver. Per the manual this causes the device to send regular data packets over the following 20 minutes.

Some of those samples were just noise, but several looked to have been broadcast at around the time I paired the device, including this one: g100_433.92M_1000k.cu8.zip

Opening it in URH, with a manually tuned FSK centre frequency, I managed to extract a clean bitstream.

Image

Using some throwaway Python that Claude put together based on the implementation detail in oil_watchman_advanced.c, I confirmed this bitstream was in the expected format for this device - it included a serial number matching the one printed on my device, and CRC validates perfectly.

So in theory, the rtl_433 binary should be able to extract this data too.

Iterating with -X flex decoders at the known-correct 500µs symbol width against this known-good sample showed -Y minmax's pulse-level edge detection recovering only a handful of bits. Claude's assessment of this was that the problem lives upstream of bit-slicing, in the FSK pulse detector itself, not in decoder logic or timing parameters.

Fix

The solution Claude suggested, and which seems to work, was to modify pulse_detect_fsk_minmax(). In the bot's own words:

Found the exact root cause, and it's a real limitation in the algorithm, not something exposed as a flag. pulse_detect_fsk_minmax tracks an adaptive midpoint (mid = (max+min)/2) between the two FSK frequency states — but on every single sample it decays whichever extreme was just seen by a fixed constant of 10 (var_test_max -= 10 / var_test_min += 10). That's a leaky peak-tracker: it works fine while bits keep alternating (each transition refreshes both extremes), but during a run of several consecutive same-value bits — exactly what the sync word and data bytes contain, unlike the alternating preamble — the tracked extremes decay and the midpoint drifts, until it eventually loses lock. That's precisely why we get a perfect preamble match and then garbage right at the sync word.

Since the deviation is narrow to begin with (your Center reading was only ~16kHz off), a fixed decay-per-sample eats through that margin much faster than it would for a typical wide-deviation device. This isn't tunable via any -Y flag — it's hardcoded. Let's check the actual value scale of fm_n so I can size a sensible patch.

fm_n is in scaled units where the full int16 range spans the entire ±Nyquist frequency range — at our 1MHz sample rate, that's about 15.3 Hz per unit. So the decay constant of "10 per sample" is actually ~153 Hz of drift per sample, which over just one 500-sample bit period could total ~76 kHz — vastly more than the ~20-30kHz total deviation this device actually uses. That's the smoking gun: the decay rate is tuned for wide-deviation devices and is far too aggressive for this narrow-deviation one, wiping out the adaptive window within a single bit of non-alternating data.

Let's patch it — reducing the decay constant should let the tracker hold its lock through longer non-alternating runs.

Dropping that constant value of 10 down to 1, and testing that against captured sample data causes those samples to be decoded cleanly and consistently every time, so this does seem like a plausible fix, but since I don't really understand this code, that's probably for someone else to say.

The patch we came up with adds a new command line argument to set the "decay" rate, defaulting to the original value of 10. You can see it here: https://github.com/jtopper/rtl_433/commit/35a36890e08284bd12deae809cc84c91b5b7347e

Conclusion

This fix seems to work for my use case, but maybe that's just luck? I'm going to try and get this fork running on my Home Assistant server. I hope the above is useful to others, and that ultimately this tool can reliably support all Watchman devices in future.