File 0001-fs20-Improve-bitstream-decoding-add-some-FHT-support.patch of Package rtl_433

From df48013ad62272bd49ff16d53ac534735269b18b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Br=C3=BCns?= <stefan@localhost.localdomain>
Date: Thu, 19 Aug 2021 20:23:57 +0200
Subject: [PATCH] fs20: Improve bitstream decoding, add some FHT support

The bitstream decoding is improved significantly:
- The encoding is corrected to OOK_PULSE_PWM
- Preamble scan is added, as sometimes a random leading bit is added, or
  the initial bit is dropped, both due to noise.
- MIC/sanity check is added, i.e. parity is checked for each individual
  data byte, and the 'sum' byte is checked for a sane value.

Also report the flags from the command byte, and the optional extension
byte.

The FHT protocol uses the same bitstream encoding as FS20, but can be
detected by a different range for the sum byte. Add decode tables for
cmd and flags.
---
 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 9f92c13..c2bc040 100644
--- a/src/devices/fs20.c
+++ b/src/devices/fs20.c
@@ -29,6 +29,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;
+};
+
+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 *cmd_tab[] = {
@@ -65,35 +112,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 <ext>%",
+            "? (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) {
+            printf("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;
+
+    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;
 
-    // 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 ((sum < 6) || (sum > 0xC + 2))
+      return DECODE_FAIL_SANITY;
 
     // convert address to fs20 format (base4+1)
     for (uint8_t i = 0; i < 4; i++) {
@@ -109,10 +246,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);
@@ -125,16 +264,18 @@ static char *output_fields[] = {
         "housecode",
         "address",
         "command",
+        "flags",
+        "ext",
         NULL,
 };
 
 r_device 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,
 };
-- 
2.32.0

openSUSE Build Service is sponsored by