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).
This commit is contained in:
Benjamin Larsson
2026-07-13 23:55:45 +02:00
parent e48537c525
commit 9bea552a2e

View File

@@ -38,52 +38,82 @@ Data layout:
- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id
- S: 16bit CRC
Demodulated as raw PCM (half-bit width ~136 us) with the preamble and
Manchester decoding done explicitly in decode_fn, rather than relying on
OOK_PULSE_MANCHESTER_ZEROBIT's own pulse-level auto-decode. That demod has
no preamble-alignment step of its own -- any noise before the real signal
starts (seen on some 5816/5820L/2GIG units at close range, where receiver
AGC overload makes the pre-message "gap" noisy) throws off its bit count
from the very first edge, corrupting the whole message even though the
underlying pulse train is intact. Searching for the preamble explicitly at
the raw level, as done here, recovers those messages: confirmed against
real captures in https://github.com/merbanan/rtl_433/issues/2015 that
decoded 0 of 6 repeats with the old demod and 6 of 6 with this one.
*/
static int honeywell_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
// full preamble is 0xFFFE
uint8_t const preamble_pattern[2] = {0xff, 0xe0}; // 12 bits
// full preamble is 0xFFFE decoded, i.e. 0x555556 (24 bits) raw Manchester
// before decoding -- searched for at the raw level (see file header) so
// that noise before the real signal starts can't misalign the decode.
// The preamble is a repeating 01 pattern until its final 10, so a naive
// single bitbuffer_search() can latch onto an earlier, spurious 24-bit
// match within that run at the wrong (off-by-one-bit) alignment -- loop
// over every match and validate by CRC instead of trusting the first.
uint8_t const preamble_pattern[3] = {0x55, 0x55, 0x56};
int row = 0; // we expect a single row only. reduce collisions
if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[row] < 60) {
if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[row] < 120) {
return DECODE_ABORT_LENGTH;
}
bitbuffer_invert(bitbuffer);
int raw_len = bitbuffer->bits_per_row[row];
uint8_t b[10] = {0};
int channel = 0;
int device_id = 0;
int crc = 0;
int len = 0;
unsigned raw_pos = 0;
int found = 0;
int pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, 12) + 12;
int len = bitbuffer->bits_per_row[row] - pos;
if (len < 48) {
return DECODE_ABORT_LENGTH;
while ((raw_pos = bitbuffer_search(bitbuffer, row, raw_pos, preamble_pattern, 24)) + 24 < (unsigned)raw_len) {
bitbuffer_t decoded = {0};
bitbuffer_manchester_decode(bitbuffer, row, raw_pos + 24, &decoded, 96);
raw_pos += 1; // try the next possible (possibly off-by-one) match too
len = decoded.bits_per_row[0];
if (len < 48) {
continue;
}
memcpy(b, decoded.bb[0], 10);
channel = b[0] >> 4;
device_id = ((b[0] & 0xf) << 16) | (b[1] << 8) | b[2];
crc = (b[4] << 8) | b[5];
if (device_id == 0 && crc == 0) {
continue; // Reduce collisions
}
uint16_t crc_calculated;
if (channel == 0x2 || channel == 0x4 || channel == 0x9 || channel == 0xA || channel == 0xC) {
// 2GIG brand, also Type 0xC for Tilt Sensor
crc_calculated = crc16(b, 4, 0x8050, 0);
} else { // channel == 0x8
crc_calculated = crc16(b, 4, 0x8005, 0);
}
if (crc == crc_calculated) {
found = 1;
break;
}
}
uint8_t b[10] = {0};
uint16_t crc_calculated = 0;
bitbuffer_extract_bytes(bitbuffer, row, pos, b, 80);
int channel = b[0] >> 4;
int device_id = ((b[0] & 0xf) << 16) | (b[1] << 8) | b[2];
int crc = (b[4] << 8) | b[5];
if (device_id == 0 && crc == 0) {
return DECODE_ABORT_EARLY; // Reduce collisions
if (!found) {
return DECODE_FAIL_MIC; // Not a valid packet
}
if (len > 50) { // DW11
decoder_log_bitrow(decoder, 1, __func__, b, (len > 80 ? 80 : len), "");
}
if (channel == 0x2 || channel == 0x4 || channel == 0x9 || channel == 0xA || channel == 0xC) {
// 2GIG brand, also Type 0xC for Tilt Sensor
crc_calculated = crc16(b, 4, 0x8050, 0);
} else { // channel == 0x8
crc_calculated = crc16(b, 4, 0x8005, 0);
}
if (crc != crc_calculated) {
return DECODE_FAIL_MIC; // Not a valid packet
}
int event = b[3];
// decoded event bits: CTRABHUU
// NOTE: not sure if these apply to all device types
@@ -133,10 +163,10 @@ static char const *const output_fields[] = {
r_device const honeywell = {
.name = "Honeywell Door/Window Sensor, 2Gig DW10/DW11, RE208 repeater",
.modulation = OOK_PULSE_MANCHESTER_ZEROBIT,
.short_width = 156,
.long_width = 0,
.reset_limit = 292,
.modulation = OOK_PULSE_PCM,
.short_width = 136,
.long_width = 136,
.reset_limit = 408,
.decode_fn = &honeywell_decode,
.fields = output_fields,
};