From 5ac7784e322fb6bb032ca23659664bfdec19eab7 Mon Sep 17 00:00:00 2001 From: StefanBruens Date: Sat, 10 Feb 2024 23:52:10 +0100 Subject: [PATCH] Improve FS20 decoding, add FHT support (#1783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stefan BrĂ¼ns --- src/devices/fs20.c | 185 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 163 insertions(+), 22 deletions(-) diff --git a/src/devices/fs20.c b/src/devices/fs20.c index 9345b4e1..84ecb622 100644 --- a/src/devices/fs20.c +++ b/src/devices/fs20.c @@ -31,6 +31,53 @@ Command extensions are also not decoded. feel free to improve! #include "decoder.h" +static int fs20_find_preamble(bitbuffer_t *bitbuffer, int bitpos) +{ + // Preamble is 12 x '0' | '1', but we ignore the first preamble bit + // Last bit ('1') is at position (pattern[1] >> 4 & 1) + uint8_t const preamble_pattern[2] = {0x00, 0x10}; + uint8_t const min_packet_length = 4 * (8 + 1); + + // fast scan for 8 consecutive '0' bits + uint8_t *bits = bitbuffer->bb[0]; + while ((bitpos + 12 + min_packet_length < bitbuffer->bits_per_row[0]) + && ((bits[(bitpos / 8) + 1] == 0) || (bits[(bitpos / 8)] != 0))) { + bitpos += 8; + } + if (bitpos) { + bitpos--; + bitpos &= ~0x3; + } + + while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 12)) < bitbuffer->bits_per_row[0]) { + if (bitpos + min_packet_length >= bitbuffer->bits_per_row[0]) + return DECODE_ABORT_LENGTH; + + return bitpos + 12; + } + + // preamble not found + return DECODE_FAIL_SANITY; +} + +struct parity_byte { + uint8_t data; + uint8_t err; +}; + +static struct parity_byte get_byte(uint8_t* bits, unsigned pos) +{ + uint16_t word = (bits[pos / 8] << 8) | bits[(pos / 8) + 1]; + struct parity_byte res; + + word <<= pos & 7; + res.data = word >> 8; + // parity8 returns odd parity, bit 9 is even parity + res.err = parity8(res.data) != (word >> 7 & 1); + + return res; +} + static int fs20_decode(r_device *decoder, bitbuffer_t *bitbuffer) { static char const *const cmd_tab[] = { @@ -67,35 +114,125 @@ static int fs20_decode(r_device *decoder, bitbuffer_t *bitbuffer) "unused", "unused", }; + static char const *flags_tab[8] = { + "(none)", + "Extended", + "BiDir", + "Extended | BiDir", + "Response", + "Response | Extended", + "Response | BiDir", + "Response | Extended | BiDir", + }; + static char const *fht_cmd_tab[16] = { + "end-of-sync", + "valve open", + "valve close", + "? (0x3)", + "? (0x4)", + "? (0x5)", + "valve open %", + "? (0x7)", + "offset adjust", + "? (0x9)", + "valve de-scale", + "? (0x11)", + "sync countdown", + "? (0x13)", + "beep", + "pairing?", + }; + static char const *fht_flags_tab[8] = { + "(none)", + "Extended", + "BS?", + "Extended | BS?", + "Repeat", + "Repeat | Extended", + "Repeat | BS?", + "Repeat | Extended | BS?", + }; - uint8_t *b; // bits of a row + bitbuffer_invert(bitbuffer); + + uint8_t *bits = bitbuffer->bb[0]; uint8_t cmd; - uint8_t hc1; - uint8_t hc2; uint16_t hc; uint8_t address; + uint8_t ext = 0; + uint8_t sum; + data_t *data; uint16_t ad_b4 = 0; uint32_t hc_b4 = 0; - // check length of frame - if (bitbuffer->bits_per_row[0] != 58) { - // check extended length (never tested!) - if (bitbuffer->bits_per_row[0] != 67) - return DECODE_ABORT_LENGTH; + int rc = DECODE_FAIL_MIC; + int bitpos = 0; + + while ((bitpos = fs20_find_preamble(bitbuffer, bitpos)) >= 0) { + if (decoder->verbose) { + fprintf(stderr, "Found preamble at %d\n", bitpos); + } + + struct parity_byte res; + + res = get_byte(bits, bitpos); + if (res.err) + continue; + hc = res.data << 8; + + res = get_byte(bits, bitpos + 9); + if (res.err) + continue; + hc |= res.data; + + res = get_byte(bits, bitpos + 18); + if (res.err) + continue; + address = res.data; + + res = get_byte(bits, bitpos + 27); + if (res.err) + continue; + cmd = res.data; + + res = get_byte(bits, bitpos + 36); + if (res.err) + continue; + + if (cmd & 0x20) { + ext = res.data; + if (bitpos + 45 + 9 > bitbuffer->bits_per_row[0]) + break; + + res = get_byte(bits, bitpos + 45); + if (res.err) + continue; + } + sum = res.data; + + rc = 1; + break; } - b = bitbuffer->bb[0]; - // check preamble first 13 bits '0000 0000 0000 1' - if ((b[0] != 0x00) || ((b[1] & 0xf8) != 0x08)) - return DECODE_FAIL_SANITY; + // propagate MIC + if (rc <= 0) + return rc; - // parse values from buffer - hc1 = (b[1] << 5) | (b[2] >> 3); - hc2 = (b[2] << 6) | (b[3] >> 2); - hc = hc1 << 8 | hc2; - address = (b[3] << 7) | (b[4] >> 1); - cmd = b[5] & 0x1f; + if (bitpos < 0) + return bitpos; + + // Sum is (HC1 + HC2 + Addr + Cmd [+ Ext] + Type + Repeater-Hopcount + // Type is either 6 for regular FS20 devices (switches, dimmers, ...) + // or 0xC for FHT (radiator valves) + sum -= hc >> 8; + sum -= hc & 0xff; + sum -= address; + sum -= cmd; + sum -= ext; + + if ((sum < 6) || (sum > 0xC + 2)) + return DECODE_FAIL_SANITY; // convert address to fs20 format (base4+1) for (uint8_t i = 0; i < 4; i++) { @@ -111,10 +248,12 @@ static int fs20_decode(r_device *decoder, bitbuffer_t *bitbuffer) /* clang-format off */ data = data_make( - "model", "", DATA_STRING, "FS20", + "model", "", DATA_STRING, (sum < 0xc) ? "FS20" : "FHT", "housecode", "", DATA_FORMAT, "%x", DATA_INT, hc_b4, "address", "", DATA_FORMAT, "%x", DATA_INT, ad_b4, - "command", "", DATA_STRING, cmd_tab[cmd], + "command", "", DATA_STRING, (sum < 0xc) ? cmd_tab[cmd & 0x1f] : fht_cmd_tab[cmd & 0xf], + "flags", "", DATA_STRING, (sum < 0xc) ? flags_tab[cmd >> 5] : fht_flags_tab[cmd >> 5], + "ext", "", DATA_FORMAT, "%x", DATA_INT, ext, NULL); /* clang-format on */ decoder_output_data(decoder, data); @@ -127,16 +266,18 @@ static char const *const output_fields[] = { "housecode", "address", "command", + "flags", + "ext", NULL, }; r_device const fs20 = { .name = "FS20", - .modulation = OOK_PULSE_PPM, + .modulation = OOK_PULSE_PWM, .short_width = 400, .long_width = 600, .reset_limit = 9000, .decode_fn = &fs20_decode, - .disabled = 1, // missing MIC and no sample data + .disabled = 0, .fields = output_fields, };