Support for Arris protocol. (#1598)

* Add `sendArris()` & `decodeArris()`.
  - Uses Manchester Encoding.
  - Checksum validation on decode.
  - provide release status (via `address`) and the likely `command`.
* Add helper routines:
  - `toggleArrisRelease()` for changing mesg status between a press & a release of a button.
  - `encodeArris()` to construct a valid ARRIS mesg from a `command` and a `release` status.
* Add & update unit tests.
This commit is contained in:
David Conran
2021-09-09 10:57:08 +10:00
committed by GitHub
parent a359a43367
commit aef67af9a7
9 changed files with 375 additions and 7 deletions

View File

@@ -1036,6 +1036,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Bose decode");
if (decodeBose(results, offset)) return true;
#endif // DECODE_BOSE
#if DECODE_ARRIS
DPRINTLN("Attempting Arris decode");
if (decodeArris(results, offset)) return true;
#endif // DECODE_ARRIS
// Typically new protocols are added above this line.
}
#if DECODE_HASH
@@ -1811,6 +1815,7 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr,
const int16_t excess,
const bool MSBfirst,
const bool GEThomas) {
DPRINTLN("DEBUG: Entered matchManchesterData");
uint16_t offset = 0;
uint64_t data = 0;
uint16_t nr_half_periods = 0;
@@ -1824,7 +1829,10 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr,
uint16_t min_remaining = nbits;
// Check if there is enough capture buffer to possibly have the message.
if (remaining < min_remaining) return 0; // Nope, so abort.
if (remaining < min_remaining) {
DPRINTLN("DEBUG: Ran out of capture buffer!");
return 0; // Nope, so abort.
}
// Convert to ticks. Optimisation: Saves on math/extra instructions later.
uint16_t bank = starting_balance / kRawTick;
@@ -1847,22 +1855,39 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr,
while ((offset < remaining || bank) &&
nr_half_periods < expected_half_periods) {
// Get the next entry if we haven't anything existing to process.
DPRINT("DEBUG: Offset = ");
DPRINTLN(offset);
if (!bank) bank = *(data_ptr + offset++);
DPRINT("DEBUG: Bank = ");
DPRINTLN(bank * kRawTick);
// Check if we don't have a short interval.
if (!match(bank, half_period, tolerance, excess)) return 0; // Not valid.
DPRINTLN("DEBUG: Checking for short interval");
if (!match(bank, half_period, tolerance, excess)) {
DPRINTLN("DEBUG: It is. Exiting");
return 0; // Not valid.
}
// We've succeeded in matching half a period, so count it.
nr_half_periods++;
DPRINT("DEBUG: Half Periods = ");
DPRINTLN(nr_half_periods);
// We've now used up our bank, so refill it with the next item, unless we
// are at the end of the capture buffer.
// If we are assume a single half period of "space".
if (offset < remaining)
if (offset < remaining) {
DPRINT("DEBUG: Offset = ");
DPRINTLN(offset);
bank = *(data_ptr + offset++);
else if (offset == remaining)
} else if (offset == remaining) {
bank = raw_half_period;
else
} else {
return 0; // We are out of buffer, so abort!
}
DPRINT("DEBUG: Bank = ");
DPRINTLN(bank * kRawTick);
// Shift the data along and add our new bit.
DPRINT("DEBUG: Adding bit: ");
DPRINTLN((currentBit ? "1" : "0"));
data <<= 1;
data |= currentBit;
@@ -1870,10 +1895,12 @@ uint16_t IRrecv::matchManchesterData(volatile const uint16_t *data_ptr,
if (match(bank, half_period * 2, tolerance, excess)) {
// It is, so flip the bit we need to append, and remove a half_period of
// time from the bank.
DPRINTLN("DEBUG: long interval detected");
currentBit = !currentBit;
bank -= raw_half_period;
} else if (match(bank, half_period, tolerance, excess)) {
// It is a short interval, so eat up all the time and move on.
DPRINTLN("DEBUG: short interval detected");
bank = 0;
} else if (nr_half_periods == expected_half_periods - 1 &&
matchAtLeast(bank, half_period, tolerance, excess)) {

View File

@@ -287,6 +287,10 @@ class IRrecv {
bool decodeArgo(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArgoBits, const bool strict = true);
#endif // DECODE_ARGO
#if DECODE_ARRIS
bool decodeArris(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kArrisBits, const bool strict = true);
#endif // DECODE_ARRIS
#if DECODE_SONY
bool decodeSony(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kSonyMinBits,

View File

@@ -790,6 +790,13 @@
#define SEND_BOSE _IR_ENABLE_DEFAULT_
#endif // SEND_BOSE
#ifndef DECODE_ARRIS
#define DECODE_ARRIS _IR_ENABLE_DEFAULT_
#endif // DECODE_ARRIS
#ifndef SEND_ARRIS
#define SEND_ARRIS _IR_ENABLE_DEFAULT_
#endif // SEND_ARRIS
#if (DECODE_ARGO || DECODE_DAIKIN || DECODE_FUJITSU_AC || DECODE_GREE || \
DECODE_KELVINATOR || DECODE_MITSUBISHI_AC || DECODE_TOSHIBA_AC || \
DECODE_TROTEC || DECODE_HAIER_AC || DECODE_HITACHI_AC || \
@@ -951,8 +958,9 @@ enum decode_type_t {
TROTEC_3550,
SANYO_AC88, // 105
BOSE,
ARRIS,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = BOSE,
kLastDecodeType = ARRIS,
};
// Message lengths & required repeat values
@@ -970,6 +978,7 @@ const uint16_t kAmcorDefaultRepeat = kSingleRepeat;
const uint16_t kArgoStateLength = 12;
const uint16_t kArgoBits = kArgoStateLength * 8;
const uint16_t kArgoDefaultRepeat = kNoRepeat;
const uint16_t kArrisBits = 32;
const uint16_t kCoolixBits = 24;
const uint16_t kCoolixDefaultRepeat = kSingleRepeat;
const uint16_t kCarrierAcBits = 32;

View File

@@ -637,6 +637,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
case LG:
case LG2:
return 28;
case ARRIS:
case CARRIER_AC:
case ELITESCREENS:
case EPSON:
@@ -790,7 +791,12 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
case AIWA_RC_T501:
sendAiwaRCT501(data, nbits, min_repeat);
break;
#endif
#endif // SEND_AIWA_RC_T501
#if SEND_ARRIS
case ARRIS:
sendArris(data, nbits, min_repeat);
break;
#endif // SEND_ARRIS
#if SEND_BOSE
case BOSE:
sendBose(data, nbits, min_repeat);

View File

@@ -737,6 +737,12 @@ class IRsend {
void sendBose(const uint64_t data, const uint16_t nbits = kBoseBits,
const uint16_t repeat = kNoRepeat);
#endif // SEND_BOSE
#if SEND_ARRIS
void sendArris(const uint64_t data, const uint16_t nbits = kArrisBits,
const uint16_t repeat = kNoRepeat);
static uint32_t toggleArrisRelease(const uint32_t data);
static uint32_t encodeArris(const uint32_t command, const bool release);
#endif // SEND_ARRIS
protected:
#ifdef UNIT_TEST

View File

@@ -293,5 +293,6 @@ const PROGMEM char *kAllProtocolNamesStr =
D_STR_TROTEC_3550 "\x0"
D_STR_SANYO_AC88 "\x0"
D_STR_BOSE "\x0"
D_STR_ARRIS "\x0"
///< New protocol strings should be added just above this line.
"\x0"; ///< This string requires double null termination.

123
src/ir_Arris.cpp Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2021 David Conran
#include "IRrecv.h"
#include "IRsend.h"
#include "IRutils.h"
/// @file
/// @brief Arris "Manchester code" based protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
// Supports:
// Brand: Arris, Model: VIP1113M Set-top box
// Brand: Arris, Model: 120A V1.0 A18 remote
const uint8_t kArrisOverhead = 2;
const uint16_t kArrisHalfClockPeriod = 320; // uSeconds
const uint16_t kArrisHdrMark = 8 * kArrisHalfClockPeriod; // uSeconds
const uint16_t kArrisHdrSpace = 6 * kArrisHalfClockPeriod; // uSeconds
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595#issuecomment-913755841
// aka. 77184 uSeconds.
const uint32_t kArrisGapSpace = 102144 - ((8 + 6 + kArrisBits * 2) *
kArrisHalfClockPeriod); // uSeconds
const uint32_t kArrisReleaseToggle = 0x800008;
const uint8_t kArrisChecksumSize = 4;
const uint8_t kArrisCommandSize = 19;
const uint8_t kArrisReleaseBit = kArrisChecksumSize + kArrisCommandSize;
using irutils::sumNibbles;
#if SEND_ARRIS
/// Send an Arris Manchester Code formatted message.
/// Status: STABLE / Confirmed working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of the message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
void IRsend::sendArris(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header (part 1)
mark(kArrisHdrMark);
space(kArrisHdrSpace);
// Header (part 2) + Data + Footer
sendManchester(kArrisHalfClockPeriod * 2, 0, kArrisHalfClockPeriod,
0, kArrisGapSpace, data, nbits);
}
}
/// Flip the toggle button release bits of an Arris message.
/// Used to indicate a change of remote button's state. e.g. Press vs. Release.
/// @param[in] data The existing Arris message.
/// @return A data message suitable for use in sendArris() with the release bits
/// flipped.
uint32_t IRsend::toggleArrisRelease(const uint32_t data) {
return data ^ kArrisReleaseToggle;
}
/// Construct a raw 32-bit Arris message code from the supplied command &
/// release setting.
/// @param[in] command The command code.
/// @param[in] release The button/command action: press (false), release (true)
/// @return A raw 32-bit Arris message code suitable for sendArris() etc.
/// @note Sequence of bits = header + release + command + checksum.
uint32_t IRsend::encodeArris(const uint32_t command, const bool release) {
uint32_t result = 0x10000000;
irutils::setBits(&result, kArrisChecksumSize, kArrisCommandSize, command);
irutils::setBit(&result, kArrisReleaseBit, release);
return result + sumNibbles(result);
}
#endif // SEND_ARRIS
#if DECODE_ARRIS
/// Decode the supplied Arris "Manchester code" message.
/// Status: STABLE / Confirmed working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
/// @param[in] offset The starting index to use when attempting to decode the
/// raw data. Typically/Defaults to kStartOffset.
/// @param[in] nbits The number of data bits to expect.
/// @param[in] strict Flag indicating if we should perform strict matching.
/// @return A boolean. True if it can decode it, false if it can't.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
bool IRrecv::decodeArris(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < nbits + kArrisOverhead - offset)
return false; // Too short a message to match.
// Compliance
if (strict && nbits != kArrisBits)
return false; // Doesn't match our protocol defn.
// Header (part 1)
if (!matchMark(results->rawbuf[offset++], kArrisHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kArrisHdrSpace)) return false;
// Header (part 2) + Data
uint64_t data = 0;
if (!matchManchester(results->rawbuf + offset, &data,
results->rawlen - offset, nbits,
kArrisHalfClockPeriod * 2, 0,
kArrisHalfClockPeriod, 0, 0,
false, kUseDefTol, kMarkExcess, true, false))
return false;
// Compliance
if (strict)
// Validate the checksum.
if (GETBITS32(data, 0, kArrisChecksumSize) !=
sumNibbles(data >> kArrisChecksumSize))
return false;
// Success
results->decode_type = decode_type_t::ARRIS;
results->bits = nbits;
results->value = data;
// Set the address as the Release Bit for something useful.
results->address = static_cast<bool>(GETBIT32(data, kArrisReleaseBit));
// The last 4 bits are likely a checksum value, so skip those. Everything else
// after the release bit. e.g. Bits 10-28
results->command = GETBITS32(data, kArrisChecksumSize, kArrisCommandSize);
return true;
}
#endif // DECODE_ARRIS

View File

@@ -508,6 +508,9 @@
#ifndef D_STR_ARGO
#define D_STR_ARGO "ARGO"
#endif // D_STR_ARGO
#ifndef D_STR_ARRIS
#define D_STR_ARRIS "ARRIS"
#endif // D_STR_ARRIS
#ifndef D_STR_BOSE
#define D_STR_BOSE "BOSE"
#endif // D_STR_BOSE

189
test/ir_Arris_test.cpp Normal file
View File

@@ -0,0 +1,189 @@
// Copyright 2021 David Conran
#include "IRac.h"
#include "IRrecv.h"
#include "IRrecv_test.h"
#include "IRsend.h"
#include "IRsend_test.h"
#include "gtest/gtest.h"
// Tests for decodeArris().
// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1595
// Data from:
// https://github.com/crankyoldgit/IRremoteESP8266/files/7100289/raw_data.txt
TEST(TestDecodeArris, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
const uint16_t rawData_1[59] = {
2584, 1896, 666, 306, 338, 300, 336, 304, 668, 610, 332, 306, 338, 300,
334, 304, 332, 312, 332, 306, 340, 300, 334, 304, 330, 308, 338, 302, 334,
304, 330, 308, 336, 308, 336, 302, 332, 306, 330, 310, 674, 606, 336, 302,
332, 306, 338, 306, 668, 612, 668, 306, 338, 304, 332, 308, 336, 608,
334}; // UNKNOWN EDF1C0D0
irsend.begin();
irsend.reset();
irsend.sendRaw(rawData_1, 59, 38);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(decode_type_t::ARRIS, irsend.capture.decode_type);
EXPECT_EQ(kArrisBits, irsend.capture.bits);
EXPECT_EQ(0x1000085E, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x85, irsend.capture.command);
irsend.reset();
const uint16_t rawData_2[115] = {
2584, 1898, 664, 308, 338, 302, 332, 306, 668, 614, 330, 308, 336, 302,
332, 306, 340, 304, 330, 310, 336, 304, 332, 306, 338, 300, 334, 304, 330,
308, 336, 302, 332, 312, 334, 306, 330, 308, 336, 302, 670, 610, 332, 306,
330, 310, 336, 308, 674, 606, 664, 312, 334, 306, 338, 302, 334, 612, 330,
5930,
2584, 1898, 664, 308, 336, 302, 332, 306, 666, 614, 338, 300, 336, 304,
332, 310, 674, 610, 332, 334, 312, 328, 308, 332, 304, 336, 310, 330, 306,
332, 302, 314, 330, 336, 308, 330, 306, 334, 640, 612, 330, 308, 336, 302,
332, 312, 672, 608, 672, 608, 672, 304, 330, 614, 330
}; // UNKNOWN E6A77D83
irsend.sendRaw(rawData_2, 115, 38);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(decode_type_t::ARRIS, irsend.capture.decode_type);
EXPECT_EQ(kArrisBits, irsend.capture.bits);
EXPECT_EQ(0x1000085E, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x85, irsend.capture.command);
const uint16_t rawData_3[51] = {
2584, 1896, 666, 308, 338, 328, 306, 332, 640, 612, 332, 336, 310, 300,
334, 304, 678, 606, 336, 330, 306, 334, 310, 300, 334, 304, 332, 308, 338,
302, 334, 310, 672, 304, 332, 614, 668, 612, 330, 336, 638, 620, 670, 610,
670, 304, 330, 310, 336, 610, 672}; // UNKNOWN 4CA048A1
irsend.reset();
irsend.sendRaw(rawData_3, 51, 38);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(decode_type_t::ARRIS, irsend.capture.decode_type);
EXPECT_EQ(kArrisBits, irsend.capture.bits);
EXPECT_EQ(0x1080695D, irsend.capture.value);
EXPECT_EQ(0x1, irsend.capture.address);
EXPECT_EQ(0x695, irsend.capture.command);
}
TEST(TestDecodeArris, SyntheticExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();
irsend.reset();
irsend.sendArris(0x1000085E);
irsend.makeDecodeResult();
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::ARRIS, irsend.capture.decode_type);
EXPECT_EQ(kArrisBits, irsend.capture.bits);
EXPECT_EQ(0x1000085E, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x85, irsend.capture.command);
EXPECT_EQ(
"f38000d50"
// const uint16_t rawData_1[59] = {
// 2584, 1896,
"m2560s1920"
// 666, 306,
"m640s320"
// 338, 300,
"m320s320"
// 336, 304,
"m320s320"
// 668, 610,
"m640s640"
// 332, 306,
"m320s320"
// 338, 300,
"m320s320"
// 334, 304,
"m320s320"
// 332, 312,
"m320s320"
// 332, 306,
"m320s320"
// 340, 300,
"m320s320"
// 334, 304,
"m320s320"
// 330, 308,
"m320s320"
// 338, 302,
"m320s320"
// 334, 304,
"m320s320"
// 330, 308,
"m320s320"
// 336, 308,
"m320s320"
// 336, 302,
"m320s320"
// 332, 306,
"m320s320"
// 330, 310,
"m320s320"
// 674, 606,
"m640s640"
// 336, 302,
"m320s320"
// 332, 306,
"m320s320"
// 338, 306,
"m320s320"
// 668, 612,
"m640s640"
// 668, 306,
"m640s320"
// 338, 304,
"m320s320"
// 332, 308,
"m320s320"
// 336, 608,
"m320s640"
// 334}; // UNKNOWN EDF1C0D0
"m320s77184", irsend.outputStr());
irsend.reset();
irsend.sendArris(0x1080695D);
irsend.makeDecodeResult();
EXPECT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(decode_type_t::ARRIS, irsend.capture.decode_type);
EXPECT_EQ(kArrisBits, irsend.capture.bits);
EXPECT_EQ(0x1080695D, irsend.capture.value);
EXPECT_EQ(0x1, irsend.capture.address);
EXPECT_EQ(0x695, irsend.capture.command);
}
TEST(TestUtils, Housekeeping) {
ASSERT_EQ("ARRIS", typeToString(decode_type_t::ARRIS));
ASSERT_EQ(decode_type_t::ARRIS, strToDecodeType("ARRIS"));
ASSERT_FALSE(hasACState(decode_type_t::ARRIS));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::ARRIS));
ASSERT_EQ(kArrisBits, IRsend::defaultBits(decode_type_t::ARRIS));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::ARRIS));
}
TEST(TestSendArris, ReleaseToggle) {
EXPECT_EQ(0x10800F5D, IRsend::toggleArrisRelease(0x10000F55));
EXPECT_EQ(0x10000F55, IRsend::toggleArrisRelease(0x10800F5D));
EXPECT_EQ(
0x10800F5D,
IRsend::toggleArrisRelease(IRsend::toggleArrisRelease(0x10800F5D)));
}
TEST(TestSendArris, encodeArris) {
EXPECT_EQ(0x10800F5D, IRsend::encodeArris(0xF5, true));
EXPECT_EQ(0x10000F55, IRsend::encodeArris(0xF5, false));
EXPECT_EQ(0x1080695D, IRsend::encodeArris(0x695, true));
}