rtl_433/src/devices/radiohead_ask.c

270 lines
9.2 KiB
C

/** @file
RadioHead ASK (generic) protocol.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
/** @fn int radiohead_ask_callback(r_device *decoder, bitbuffer_t *bitbuffer)
RadioHead ASK (generic) protocol.
Default transmitter speed is 2000 bits per second, i.e. 500 us per bit.
The symbol encoding ensures a maximum run (gap) of 4x bit-width.
Sensible Living uses a speed of 1000, i.e. 1000 us per bit.
*/
#include "decoder.h"
// Maximum message length (including the headers, byte count and FCS) we are willing to support
// This is pretty arbitrary
#define RH_ASK_MAX_PAYLOAD_LEN 67
#define RH_ASK_HEADER_LEN 4
#define RH_ASK_MAX_MESSAGE_LEN (RH_ASK_MAX_PAYLOAD_LEN - RH_ASK_HEADER_LEN - 3)
// Note: all the "4to6 code" came from RadioHead source code.
// see: http://www.airspayce.com/mikem/arduino/RadioHead/index.html
// 4 bit to 6 bit symbol converter table
// Used to convert the high and low nybbles of the transmitted data
// into 6 bit symbols for transmission. Each 6-bit symbol has 3 1s and 3 0s
// with at most 3 consecutive identical bits.
// Concatenated symbols have runs of at most 4 identical bits.
static uint8_t const symbols[] = {
0x0d, 0x0e, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,
0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34
};
// Convert a 6 bit encoded symbol into its 4 bit decoded equivalent
static uint8_t symbol_6to4(uint8_t symbol)
{
uint8_t i;
// Linear search :-( Could have a 64 byte reverse lookup table?
// There is a little speedup here courtesy Ralph Doncaster:
// The shortcut works because bit 5 of the symbol is 1 for the last 8
// symbols, and it is 0 for the first 8.
// So we only have to search half the table
for (i = (symbol >> 2) & 8; i < 16; i++) {
if (symbol == symbols[i])
return i;
}
return 0xFF; // Not found
}
/**
Radiohead ASK parser.
*/
static int radiohead_ask_extract(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t row, /*OUT*/ uint8_t *payload)
{
int len = bitbuffer->bits_per_row[row];
int msg_len = RH_ASK_MAX_MESSAGE_LEN;
int pos, nb_bytes;
uint8_t rxBits[2] = {0};
uint16_t crc, crc_recompute;
// Looking for preamble
uint8_t const init_pattern[] = {
0x55, // 8
0x55, // 16
0x55, // 24
0x51, // 32
0xcd, // 40
};
// The first 0 is ignored by the decoder, so we look only for 28 bits of "01"
// and not 32. Also "0x1CD" is 0xb38 (RH_ASK_START_SYMBOL) with LSBit first.
int init_pattern_len = 40;
pos = bitbuffer_search(bitbuffer, row, 0, init_pattern, init_pattern_len);
if (pos == len) {
decoder_log(decoder, 2, __func__, "preamble not found");
return DECODE_ABORT_EARLY;
}
// read "bytes" of 12 bit
nb_bytes = 0;
pos += init_pattern_len;
for (; pos < len && nb_bytes < msg_len; pos += 12) {
bitbuffer_extract_bytes(bitbuffer, row, pos, rxBits, /*len=*/16);
// ^ we should read 16 bits and not 12, elsewhere last 4bits are ignored
rxBits[0] = reverse8(rxBits[0]);
rxBits[1] = reverse8(rxBits[1]);
rxBits[1] = ((rxBits[1] & 0x0F) << 2) + (rxBits[0] >> 6);
rxBits[0] &= 0x3F;
uint8_t hi_nibble = symbol_6to4(rxBits[0]);
if (hi_nibble > 0xF) {
decoder_logf(decoder, 1, __func__, "Error on 6to4 decoding high nibble: %X", rxBits[0]);
return DECODE_FAIL_SANITY;
}
uint8_t lo_nibble = symbol_6to4(rxBits[1]);
if (lo_nibble > 0xF) {
decoder_logf(decoder, 1, __func__, "Error on 6to4 decoding low nibble: %X", rxBits[1]);
return DECODE_FAIL_SANITY;
}
uint8_t byte = hi_nibble << 4 | lo_nibble;
payload[nb_bytes] = byte;
if (nb_bytes == 0) {
msg_len = byte;
// abort on invalid message length byte
if (msg_len < 2 || msg_len > RH_ASK_MAX_MESSAGE_LEN) {
break;
}
}
nb_bytes++;
}
// Prevent buffer underflow when calculating CRC
if (msg_len < 2) {
decoder_log(decoder, 2, __func__, "message too short to contain crc");
return DECODE_ABORT_LENGTH;
}
// Sanity check on excessive msg len
if (msg_len > RH_ASK_MAX_MESSAGE_LEN) {
decoder_logf(decoder, 2, __func__, "message too long: %d", msg_len);
return DECODE_ABORT_LENGTH;
}
// Check CRC
crc = (payload[msg_len - 1] << 8) | payload[msg_len - 2];
crc_recompute = ~crc16lsb(payload, msg_len - 2, 0x8408, 0xFFFF);
if (crc_recompute != crc) {
decoder_logf(decoder, 1, __func__, "CRC error: %04X != %04X", crc_recompute, crc);
return DECODE_FAIL_MIC;
}
return msg_len;
}
static int radiohead_ask_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
data_t *data;
uint8_t row = 0; // we are considering only first row
int msg_len, data_len, header_to, header_from, header_id, header_flags;
uint8_t rh_payload[RH_ASK_MAX_PAYLOAD_LEN] = {0};
int rh_data_payload[RH_ASK_MAX_MESSAGE_LEN];
msg_len = radiohead_ask_extract(decoder, bitbuffer, row, rh_payload);
if (msg_len <= 0) {
return msg_len; // pass error code on
}
data_len = msg_len - RH_ASK_HEADER_LEN - 3;
if (data_len <= 0)
return DECODE_FAIL_SANITY;
header_to = rh_payload[1];
header_from = rh_payload[2];
header_id = rh_payload[3];
header_flags = rh_payload[4];
// Format data
for (int j = 0; j < data_len; j++) {
rh_data_payload[j] = (int)rh_payload[5 + j];
}
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "RadioHead-ASK",
"len", "Data len", DATA_INT, data_len,
"to", "To", DATA_INT, header_to,
"from", "From", DATA_INT, header_from,
"id", "Id", DATA_INT, header_id,
"flags", "Flags", DATA_INT, header_flags,
"payload", "Payload", DATA_ARRAY, data_array(data_len, DATA_INT, rh_data_payload),
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
/**
Sensible Living Mini-Plant Moisture Sensor.
@todo Documentation needed.
*/
static int sensible_living_callback(r_device *decoder, bitbuffer_t *bitbuffer)
{
data_t *data;
uint8_t row = 0; // we are considering only first row
int msg_len, house_id, sensor_type, sensor_count, alarms;
int module_id, sensor_value, battery_voltage;
uint8_t rh_payload[RH_ASK_MAX_PAYLOAD_LEN] = {0};
msg_len = radiohead_ask_extract(decoder, bitbuffer, row, rh_payload);
if (msg_len <= 0) {
return msg_len; // pass error code on
}
house_id = rh_payload[1];
module_id = (rh_payload[2] << 8) | rh_payload[3];
sensor_type = rh_payload[4];
sensor_count = rh_payload[5];
alarms = rh_payload[6];
sensor_value = (rh_payload[7] << 8) | rh_payload[8];
battery_voltage = (rh_payload[9] << 8) | rh_payload[10];
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "SensibleLiving-Moisture",
"house_id", "House ID", DATA_INT, house_id,
"module_id", "Module ID", DATA_INT, module_id,
"sensor_type", "Sensor Type", DATA_INT, sensor_type,
"sensor_count", "Sensor Count", DATA_INT, sensor_count,
"alarms", "Alarms", DATA_INT, alarms,
"sensor_value", "Sensor Value", DATA_INT, sensor_value,
"battery_mV", "Battery Voltage", DATA_INT, battery_voltage * 10,
"mic", "Integrity", DATA_STRING, "CRC",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
static char const *const radiohead_ask_output_fields[] = {
"model",
"len",
"to",
"from",
"id",
"flags",
"payload",
"mic",
NULL,
};
static char const *const sensible_living_output_fields[] = {
"model",
"house_id",
"module_id",
"sensor_type",
"sensor_count",
"alarms",
"sensor_value",
"battery_mV",
"mic",
NULL,
};
r_device const radiohead_ask = {
.name = "Radiohead ASK",
.modulation = OOK_PULSE_PCM,
.short_width = 500,
.long_width = 500,
.reset_limit = 5 * 500,
.decode_fn = &radiohead_ask_callback,
.fields = radiohead_ask_output_fields,
};
r_device const sensible_living = {
.name = "Sensible Living Mini-Plant Moisture Sensor",
.modulation = OOK_PULSE_PCM,
.short_width = 1000,
.long_width = 1000,
.reset_limit = 5 * 1000,
.decode_fn = &sensible_living_callback,
.fields = sensible_living_output_fields,
};