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