diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index 9c7519cbd..4b208c031 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -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)) { diff --git a/src/IRrecv.h b/src/IRrecv.h index 1a883d509..4c81a87dc 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -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, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 35b45d958..f49d313c6 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -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; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 4814bd431..8b79707c7 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -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); diff --git a/src/IRsend.h b/src/IRsend.h index c20fc64bc..82887a6a9 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -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 diff --git a/src/IRtext.cpp b/src/IRtext.cpp index a7bd7de29..008b102b5 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -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. diff --git a/src/ir_Arris.cpp b/src/ir_Arris.cpp new file mode 100644 index 000000000..5b39808c8 --- /dev/null +++ b/src/ir_Arris.cpp @@ -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(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 diff --git a/src/locale/defaults.h b/src/locale/defaults.h index b55b87abc..bae02265a 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -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 diff --git a/test/ir_Arris_test.cpp b/test/ir_Arris_test.cpp new file mode 100644 index 000000000..2a219b6b2 --- /dev/null +++ b/test/ir_Arris_test.cpp @@ -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)); +}