mirror of https://github.com/merbanan/rtl_433.git
270 lines
9.2 KiB
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,
|
|
};
|