rtl_433/src/devices/directv.c
2021-06-12 12:05:07 +02:00

396 lines
15 KiB
C

/** @file
DirecTV RC66RX Remote Control decoder.
Copyright (C) 2019 Karl Lohner <klohner@thespill.com>
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 directv_decode(r_device *decoder, bitbuffer_t *bitbuffer)
DirecTV RC66RX Remote Control decoder.
The device uses FSK to transmit a PCM signal TRANSMISSION. Its FSK signal
seems to be centered around 433.92 MHz with its MARK and SPACE frequencies
each +/- 50 kHz from that center point.
A full signal TRANSMISSION consists of ROWS, which are collections of SYMBOLS.
SYMBOLS, both the higher-frequency MARK (`1`) and lower-frequency SPACE
(`0`), have a width of 600μs. If there is more than one ROW in a single
TRANSMISSION, there will be a GAP of 27,600μs of silence between each ROW.
A TRANSMISSION may be generated in response to an EVENT on the remote. Observed
EVENTS that may trigger a TRANSMISSION seem limited to manual button presses.
Each ROW in the TRANSMISSION consists of two ordered parts -- its SYNC and its
MESSAGE. Each ROW is expected to be complete; the device does not seem to ever
truncate a signal inside of a ROW.
The SYNC may be either a LONG SYNC or a SHORT SYNC. The LONG SYNC consists of
SYMBOLS `000111111111100`. It is used in each row to signify that the MESSAGE
which follows will be the first time this unique MESSAGE will be seen in
this TRANSMISSION.
However, if a unique MESSAGE is to be sent more than once in a
TRANSMISSION, each subsequent ROW with this repeated MESSAGE will send a
SHORT SYNC instead of a LONG SYNC. A SHORT SYNC consists of SYMBOLS
`0001111100`.
ROWS are typically repeated for the duration of the EVENT (a button push on the
remote) and a ROW is allowed to finish sending even if the EVENT ends before the
ROW is completely sent.
ROWS in any single TRANSMISSION usually contain the same MESSAGE, however this
is not always the case. TRANSMISSIONS may be one ROW for some short EVENTS,
although some specific EVENTS generate TRANSMISSIONS of three rows, regardless
the duration of the EVENT. Single TRANSMISSIONS have been observed to switch
from one MESSAGE to another. This seems to happen for specific buttons, such as
the [SELECT] button, which sends a single ROW containing a LONG SYNC and a
MESSAGE that encodes a new [SELECT RELEASE] MESSAGE. Some buttons send one
MESSAGE during the initial duration of the EVENT, but then switch to a new
MESSAGE if the EVENT continues. Some TRANSMISSIONS stop sending ROWS after a
duration even if the EVENT continues.
LOGICAL DATA in the MESSAGE may be decoded from the ROW using some sort of
Differential Pulse Width Modulation (DPWM) method. Between each SYMBOL
transition (both `1` to `0` and `0` to `1`) consider the number of SYMBOLS. If
there is only one SYMBOL, the LOGICAL DATA bit is a `0`. If there are two
SYMBOLS, the LOGICAL DATA bit is a `1`. If there are 3 or more SYMBOLS, this is
not DATA - it is a sync pulse. If a sync pulse is found (and is followed by
more SYMBOLS i.e. the SYMBOL does not occur at the end of the ROW), both it and
the one or two contiguous SYMBOLS after it are ignored and LOGICAL DATA would
resume decoding from that next transition.
After decoding, there should be 40 bits (5 bytes) of LOGICAL DATA.
LOGICAL DATA layout in nibbles:
MM DD DD DB BC
| Nibble # | Letter | Description |
|----------|--------|------------- |
| 0 - 1 | MM | Model? Seems to always be 0x10 |
| 2 - 6 | DDDDD | Device ID. 0x00000 - 0xF423F are valid (000000 - 999999 in decimal) |
| 7 - 8 | BB | Button Code. 0x00 - 0xFF maps to specific buttons or functions |
| 9 | C | Checksum. Least Significant Nibble of sum of previous 9 nibbles, 0x0 - 0xF |
Flex Spec to get ROW SYMBOLS:
$ rtl_433 -R 0 -X '-X n=DirecTV,m=FSK_PCM,s=600,l=600,g=30000,r=80000'
*/
#include "decoder.h"
#define ROW_BITLEN_MIN 44 // The shortest possible fragment that can possibly decode successfully
#define ROW_BITLEN_MAX 99 // But even with a LONG SYNC and large MESSAGE value, won't be larger than this
#define ROW_SYNC_SHORT_LEN 5 // A SYNC longer than this will be considered a LONG SYNC
#define DTV_BITLEN_MAX 40 // Valid decoded data for this device will be exactly 40 bits in length
// Provide a lookup between button ID codes and their names based on observations
static const char *dtv_button_label[] = {
[0x01] = "1",
[0x02] = "2",
[0x03] = "3",
[0x04] = "4",
[0x05] = "5",
[0x06] = "6",
[0x07] = "7",
[0x08] = "8",
[0x09] = "9",
[0x0D] = "CH UP",
[0x0E] = "CH DOWN",
[0x0F] = "CH PREV",
[0x10] = "PWR",
[0x11] = "0",
[0x12] = "DASH",
[0x13] = "ENTER",
[0x14] = "DASH REPEAT",
[0x15] = "ENTER REPEAT",
[0x20] = "MENU",
[0x21] = "UP",
[0x22] = "DOWN",
[0x23] = "LEFT",
[0x24] = "RIGHT",
[0x25] = "SELECT",
[0x26] = "EXIT",
[0x27] = "BACK",
[0x28] = "GUIDE",
[0x29] = "ACTIVE",
[0x2A] = "LIST",
[0x2B] = "LIST REPEAT",
[0x2C] = "INFO REPEAT",
[0x2D] = "GUIDE REPEAT",
[0x2E] = "INFO",
[0x30] = "VCR PLAY",
[0x31] = "VCR STOP",
[0x32] = "VCR PAUSE",
[0x33] = "VCR RWD",
[0x34] = "VCR FFD",
[0x35] = "VCR REC",
[0x36] = "VCR BACK",
[0x37] = "VCR SKIP",
[0x38] = "VCR SKIP REPEAT",
[0x3A] = "VCR PLAY REPEAT",
[0x3B] = "VCR PAUSE REPEAT",
[0x3C] = "VCR RWD REPEAT",
[0x3D] = "VCR FFD REPEAT",
[0x3E] = "VCR REC REPEAT",
[0x3F] = "VCR BACK REPEAT",
[0x41] = "RED",
[0x42] = "YELLOW",
[0x43] = "GREEN",
[0x44] = "BLUE",
[0x45] = "MENU REPEAT",
[0x46] = "ACTIVE REPEAT",
[0x4A] = "RED REPEAT",
[0x4B] = "YELLOW REPEAT",
[0x4C] = "GREEN REPEAT",
[0x4D] = "BLUE REPEAT",
[0x51] = "TV: VCR ALERT",
[0x59] = "VOLUME ALERT",
[0x5A] = "AV1/AV2/TV: IR ALERT 1",
[0x5B] = "DTV: IR ALERT",
[0x5C] = "AV1/AV2/TV: IR ALERT 2",
[0x5D] = "TV: DTV ALERT",
[0x5E] = "AV1: DTV ALERT",
[0x5F] = "AV2: DTV ALERT",
[0x60] = "0 REPEAT",
[0x61] = "1 REPEAT",
[0x62] = "2 REPEAT",
[0x63] = "3 REPEAT",
[0x64] = "4 REPEAT",
[0x65] = "5 REPEAT",
[0x66] = "6 REPEAT",
[0x67] = "7 REPEAT",
[0x68] = "8 REPEAT",
[0x69] = "9 REPEAT",
[0x73] = "FORMAT",
[0x75] = "FORMAT REPEAT",
[0x80] = "DTV: DTV&TV POWER ON",
[0x81] = "DTV: DTV&TV POWER OFF",
[0xD6] = "SELECT RELEASE",
[0x100] = "unknown",
};
const char *get_dtv_button_label(uint8_t button_id)
{
const char *label = dtv_button_label[button_id];
if (!label) {
label = dtv_button_label[0x100];
}
return label;
}
/// Set a single bit in a bitrow at bit_idx position. Assume success, no bounds checking, so be careful!
/// Maybe this can graduate to bitbuffer.c someday?
void bitrow_set_bit(bitrow_t bitrow, unsigned bit_idx, unsigned bit_val)
{
if (bit_val == 0) {
bitrow[bit_idx >> 3] &= ~(1 << (7 - (bit_idx & 7)));
}
else {
bitrow[bit_idx >> 3] |= (1 << (7 - (bit_idx & 7)));
}
}
/// This is a differential PWM decode and is essentially only looking at symbol
/// transitions, not the symbols themselves. An inverted bitstring would yield the
/// same result. Note that:
///
/// - Initial contiguous alike symbol(s) is not considered data, regardless of length.
/// Essentially, the
///
/// - Any group of alike contiguous symbols with a length of 3 or more is considered
/// a sync. If this happens anywhere except at the end of bitrow, any data already
/// decoded is discarded, the length and position of the sync is noted, and data
/// decoding resumes.
///
/// - The one or two alike contiguous symbols immediately after a sync are not treated
/// as data, they are essentially there to signify the end of the sync.
///
/// Return value is length of data decoded into bitrow_buf after last sync. If bitrow
/// ends with a sync, that sync is ignored and returned data will be data before that
/// sync.
///
/// Ensure that bitrow_buf is at least as big as bitrow or data overrun might occur.
///
/// Note that sync_pos and sync_len will be modified if a sync is found. If returned
/// sync_pos is greater than start, it might mean there is data between start and
/// sync_pos. If desired, call again with bit_len = sync_pos to find this data.
///
/// Maybe this can graduate to bitbuffer.c someday?
unsigned bitrow_dpwm_decode(bitrow_t const bitrow, unsigned bit_len, unsigned start,
bitrow_t bitrow_buf, unsigned *sync_pos, unsigned *sync_len)
{
unsigned bitrow_pos;
int bitrow_buf_pos = -1;
unsigned cur_symbol_len = -1;
*sync_pos = start;
*sync_len = 0;
unsigned sync_in_progress = 1;
unsigned prev_bit = 0xff; // So it's always different than the first bit
unsigned this_bit;
for (bitrow_pos = start; bitrow_pos < bit_len; bitrow_pos++) {
this_bit = bitrow_get_bit(bitrow, bitrow_pos);
if (this_bit == prev_bit) {
if (++cur_symbol_len > 1) {
sync_in_progress = 1;
}
}
else {
if (sync_in_progress) {
*sync_len = cur_symbol_len + 1;
*sync_pos = bitrow_pos - cur_symbol_len - 1;
bitrow_buf_pos = -1;
sync_in_progress = 0;
}
else {
if (bitrow_buf_pos >= 0) {
bitrow_set_bit(bitrow_buf, bitrow_buf_pos, cur_symbol_len);
}
bitrow_buf_pos++;
}
cur_symbol_len = 0;
}
prev_bit = this_bit;
}
// If a sync was started at the end of the row, ignore it and the previous decoded bit
if (sync_in_progress) {
bitrow_buf_pos -= 1;
}
// If bad decode, just send back an empty result string.
if (bitrow_buf_pos < 0) {
bitrow_buf_pos = 0;
}
return bitrow_buf_pos;
}
static int directv_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
data_t *data;
int r; // a row index
bitrow_t bitrow; // space for a possibly modified bitbuffer row
uint8_t bit_len; // row length is variable, so need to keep track of this
bitrow_t dtv_buf= {0}; // A location for our decoded bitrow data
unsigned dtv_bit_len;
unsigned row_sync_pos;
unsigned row_sync_len;
// Signal is reset by rtl_433 before recognizing rows, so in practice, there's only one row.
// It would be useful to catch rows in signal so that there'd only be one decoded row per
// signal and we could count repeats to report the length of the signal, but that's not
// supported yet. It seems the gap between rows exceeds the ook_hysteresis threshold in
// pulse_detect.c: int16_t const ook_hysteresis = ook_threshold / 8; // ±12%
// and changing this value isn't the right direction, nor does this even work for this signal.
// Grouping rows in a signal like this will need to be supported in some other way, and this
// support is not yet available in rtl_433.
// For now, we'll decode the signal in the bitbuffer assuming it is only one row.
r = 0;
bit_len = bitbuffer->bits_per_row[r];
if ((bit_len < ROW_BITLEN_MIN) || (bit_len > ROW_BITLEN_MAX)) {
if (decoder->verbose >= 2) {
fprintf(stderr, "directv: incorrect number of bits in bitbuffer: %d (expected between %d and %d).\n", bit_len, ROW_BITLEN_MIN, ROW_BITLEN_MAX);
}
return DECODE_FAIL_SANITY;
}
bitbuffer_extract_bytes(bitbuffer, r, 0, bitrow, bit_len);
// Decode the message symbols
dtv_bit_len = bitrow_dpwm_decode(bitrow, bit_len, 0, dtv_buf, &row_sync_pos, &row_sync_len);
if (decoder->verbose >= 2) {
bitrow_printf(dtv_buf, dtv_bit_len, "directv: SYNC at pos:%u for %u symbols. DPWM Decoded Message: ", row_sync_pos, row_sync_len);
}
// Make sure we have exactly 40 bits (DTV_BITLEN_MAX)
if (dtv_bit_len != DTV_BITLEN_MAX) {
if (decoder->verbose >= 2) {
fprintf(stderr, "directv: Incorrect number of decoded bits: %u (should be %d).\n", dtv_bit_len, DTV_BITLEN_MAX);
}
return DECODE_ABORT_LENGTH;
}
// First byte should be 0x10 (model number?)
if (dtv_buf[0] != 0x10) {
if (decoder->verbose >= 2) {
fprintf(stderr, "directv: Incorrect Model ID number: 0x%02X (should be 0x10).\n", dtv_buf[0]);
}
return DECODE_FAIL_SANITY;
}
// Validate Checksum
unsigned checksum_1;
unsigned checksum_2;
checksum_1 = ( (dtv_buf[0] >> 4) + (dtv_buf[0] & 0x0F) + (dtv_buf[1] >> 4) + (dtv_buf[1] & 0x0F) +
(dtv_buf[2] >> 4) + (dtv_buf[2] & 0x0F) + (dtv_buf[3] >> 4) + (dtv_buf[3] & 0x0F) +
(dtv_buf[4] >> 4) ) & 0x0F;
checksum_2 = dtv_buf[4] & 0x0F;
if (checksum_1 != checksum_2) {
if (decoder->verbose >= 2) {
fprintf(stderr, "directv: Checksum failed: 0x%01X should match 0x%01X\n", checksum_1, checksum_2);
}
return DECODE_FAIL_MIC;
}
// Get Device ID
unsigned dtv_device_id;
dtv_device_id = dtv_buf[1] << 12 | dtv_buf[2] << 4 | dtv_buf[3] >> 4;
if (dtv_device_id > 999999) {
if (decoder->verbose >= 2) {
fprintf(stderr, "directv: Bad Device ID: %u (should be between 000000 and 999999).\n", dtv_device_id);
}
return DECODE_FAIL_SANITY;
}
// Get Button ID, assuming all byte values are valid.
uint8_t dtv_button_id;
dtv_button_id = dtv_buf[3] << 4 | dtv_buf[4] >> 4;
// Populate our return fields
/* clang-format off */
data = data_make(
"model", "", DATA_STRING, "DirecTV-RC66RX",
"id", "", DATA_FORMAT, "%06d", DATA_INT, dtv_device_id,
"button_id", "", DATA_FORMAT, "0x%02X", DATA_INT, dtv_button_id,
"button_name", "", DATA_FORMAT, "[%s]", DATA_STRING, get_dtv_button_label(dtv_button_id),
"event", "", DATA_STRING, row_sync_len > ROW_SYNC_SHORT_LEN ? "INITIAL" : "REPEAT",
"mic", "Integrity", DATA_STRING, "CHECKSUM",
NULL);
/* clang-format on */
decoder_output_data(decoder, data);
return 1;
}
static char *output_fields[] = {
"model",
"id",
"button_id",
"button_name",
"event",
"mic",
NULL,
};
r_device directv = {
.name = "DirecTV RC66RX Remote Control",
.modulation = FSK_PULSE_PCM,
.short_width = 600, // 150 samples @250k
.long_width = 600, // 150 samples @250k
.gap_limit = 30000, // gap is typically around 27,600μs, so long that rtl_433 resets
// signal decoder before recognizing row repeats in signal
.reset_limit = 50000, // maximum gap size before End Of Row [μs]
.decode_fn = &directv_decode,
.disabled = 0,
.fields = output_fields,
};