From c7e6fa303fae2fc8cc7804047b5b7b993da623b8 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sun, 8 May 2022 16:11:12 +1000 Subject: [PATCH] CARRIER_AC128: Experimental Basic support for Carrier AC 128bit protocol. * Add `sendCarrierAC128()` & `decodeCarrierAC128()` methods. * Basic unit test coverage for new protocol. * Update supported model info. For #1797 --- src/IRrecv.cpp | 4 ++ src/IRrecv.h | 6 +++ src/IRremoteESP8266.h | 15 +++++- src/IRsend.cpp | 7 +++ src/IRsend.h | 5 ++ src/IRtext.cpp | 1 + src/IRutils.cpp | 1 + src/ir_Carrier.cpp | 114 ++++++++++++++++++++++++++++++++++++++- src/ir_Carrier.h | 4 +- src/locale/defaults.h | 3 ++ test/ir_Carrier_test.cpp | 93 ++++++++++++++++++++++++++++++-- 11 files changed, 244 insertions(+), 9 deletions(-) diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index a0407132c..15f663083 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1102,6 +1102,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, DPRINTLN("Attempting Coolix 48-bit decode"); if (decodeCoolix48(results, offset)) return true; #endif // DECODE_COOLIX48 +#if DECODE_CARRIER_AC128 + DPRINTLN("Attempting Carrier AC 128-bit decode"); + if (decodeCarrierAC128(results, offset)) return true; +#endif // DECODE_CARRIER_AC128 // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index 69a21ad49..1a738d0ad 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -569,6 +569,12 @@ class IRrecv { const uint16_t nbits = kCarrierAc64Bits, const bool strict = true); #endif // DECODE_CARRIER_AC64 +#if DECODE_CARRIER_AC128 + bool decodeCarrierAC128(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kCarrierAc128Bits, + const bool strict = true); +#endif // DECODE_CARRIER_AC128 #if DECODE_GOODWEATHER bool decodeGoodweather(decode_results *results, uint16_t offset = kStartOffset, diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 574b01d3d..b943cb1ce 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -455,6 +455,13 @@ #define SEND_CARRIER_AC64 _IR_ENABLE_DEFAULT_ #endif // SEND_CARRIER_AC64 +#ifndef DECODE_CARRIER_AC128 +#define DECODE_CARRIER_AC128 _IR_ENABLE_DEFAULT_ +#endif // DECODE_CARRIER_AC128 +#ifndef SEND_CARRIER_AC128 +#define SEND_CARRIER_AC128 _IR_ENABLE_DEFAULT_ +#endif // SEND_CARRIER_AC128 + #ifndef DECODE_HAIER_AC #define DECODE_HAIER_AC _IR_ENABLE_DEFAULT_ #endif // DECODE_HAIER_AC @@ -876,7 +883,7 @@ DECODE_VOLTAS || DECODE_MIRAGE || DECODE_HAIER_AC176 || \ DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \ DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \ - DECODE_KELON168 || DECODE_HITACHI_AC296 || \ + DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \ false) // Add any DECODE to the above if it uses result->state (see kStateSizeMax) // you might also want to add the protocol to hasACState function @@ -1030,8 +1037,9 @@ enum decode_type_t { HITACHI_AC264, KELON168, HITACHI_AC296, + CARRIER_AC128, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = HITACHI_AC296, + kLastDecodeType = CARRIER_AC128, }; // Message lengths & required repeat values @@ -1061,6 +1069,9 @@ const uint16_t kCarrierAc40Bits = 40; const uint16_t kCarrierAc40MinRepeat = 2; const uint16_t kCarrierAc64Bits = 64; const uint16_t kCarrierAc64MinRepeat = kNoRepeat; +const uint16_t kCarrierAc128StateLength = 16; +const uint16_t kCarrierAc128Bits = kCarrierAc128StateLength * 8; +const uint16_t kCarrierAc128MinRepeat = kNoRepeat; const uint16_t kCoronaAcStateLengthShort = 7; const uint16_t kCoronaAcStateLength = kCoronaAcStateLengthShort * 3; const uint16_t kCoronaAcBitsShort = kCoronaAcStateLengthShort * 8; diff --git a/src/IRsend.cpp b/src/IRsend.cpp index d80b4bbf6..01b0f6e17 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -684,6 +684,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { return kArgoBits; case CORONA_AC: return kCoronaAcBits; + case CARRIER_AC128: + return kCarrierAc128Bits; case DAIKIN: return kDaikinBits; case DAIKIN128: @@ -1126,6 +1128,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state, sendArgo(state, nbytes); break; #endif // SEND_ARGO +#if SEND_CARRIER_AC128 + case CARRIER_AC128: + sendCarrierAC128(state, nbytes); + break; +#endif // SEND_CARRIER_AC128 #if SEND_CORONA_AC case CORONA_AC: sendCoronaAc(state, nbytes); diff --git a/src/IRsend.h b/src/IRsend.h index 3391a4e6f..450f26cd9 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -558,6 +558,11 @@ class IRsend { void sendCarrierAC64(uint64_t data, uint16_t nbits = kCarrierAc64Bits, uint16_t repeat = kCarrierAc64MinRepeat); #endif +#if SEND_CARRIER_AC128 + void sendCarrierAC128(const uint8_t data[], + uint16_t nbytes = kCarrierAc128StateLength, + uint16_t repeat = kCarrierAc128MinRepeat); +#endif // SEND_CARRIER_AC128 #if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176) void sendHaierAC(const unsigned char data[], const uint16_t nbytes = kHaierACStateLength, diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 9c649f916..af6081024 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -396,6 +396,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_HITACHI_AC264 "\x0" D_STR_KELON168 "\x0" D_STR_HITACHI_AC296 "\x0" + D_STR_CARRIER_AC128 "\x0" ///< New protocol strings should be added just above this line. "\x0" ///< This string requires double null termination. }; diff --git a/src/IRutils.cpp b/src/IRutils.cpp index b284a4129..806bb64d7 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -172,6 +172,7 @@ bool hasACState(const decode_type_t protocol) { // This is kept sorted by name case AMCOR: case ARGO: + case CARRIER_AC128: case CORONA_AC: case DAIKIN: case DAIKIN128: diff --git a/src/ir_Carrier.cpp b/src/ir_Carrier.cpp index f52628aae..42b45f9ab 100644 --- a/src/ir_Carrier.cpp +++ b/src/ir_Carrier.cpp @@ -1,8 +1,9 @@ -// Copyright 2018, 2020 David Conran +// Copyright 2018-2022 David Conran /// @file /// @brief Carrier protocols. /// @see CarrierAc https://github.com/crankyoldgit/IRremoteESP8266/issues/385 /// @see CarrierAc64 https://github.com/crankyoldgit/IRremoteESP8266/issues/1127 +/// @see CarrierAc128 https://github.com/crankyoldgit/IRremoteESP8266/issues/1797 #include "ir_Carrier.h" #include @@ -45,6 +46,16 @@ const uint16_t kCarrierAc64OneSpace = 1736; const uint16_t kCarrierAc64ZeroSpace = 615; const uint32_t kCarrierAc64Gap = kDefaultMessageGap; // A guess. +const uint16_t kCarrierAc128HdrMark = 4600; +const uint16_t kCarrierAc128HdrSpace = 2600; +const uint16_t kCarrierAc128Hdr2Mark = 9300; +const uint16_t kCarrierAc128Hdr2Space = 5000; +const uint16_t kCarrierAc128BitMark = 340; +const uint16_t kCarrierAc128OneSpace = 1000; +const uint16_t kCarrierAc128ZeroSpace = 400; +const uint16_t kCarrierAc128SectionGap = 20600; +const uint16_t kCarrierAc128InterSpace = 6700; +const uint16_t kCarrierAc128SectionBits = kCarrierAc128Bits / 2; #if SEND_CARRIER_AC /// Send a Carrier HVAC formatted message. @@ -533,3 +544,104 @@ stdAc::state_t IRCarrierAc64::toCommon(void) const { result.clock = -1; return result; } + +#if SEND_CARRIER_AC128 +/// Send a Carrier 128bit HVAC formatted message. +/// Status: BETA / Seems to work with tests. Needs testing agaisnt real devices. +/// @param[in] data The message to be sent. +/// @param[in] nbytes The byte size of the message being sent. +/// @param[in] repeat The number of times the message is to be repeated. +void IRsend::sendCarrierAC128(const uint8_t data[], const uint16_t nbytes, + const uint16_t repeat) { + // Min length check. + if (nbytes <= kCarrierAc128StateLength / 2) return; + + enableIROut(kCarrierAcFreq); + // Handle repeats. + for (uint16_t r = 0; r <= repeat; r++) { + // First part of the message. + // Headers + Data + SectionGap + sendGeneric(kCarrierAc128HdrMark, kCarrierAc128HdrSpace, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, + data, nbytes / 2, kCarrierAcFreq, false, 0, kDutyDefault); + // Inter-message markers + mark(kCarrierAc128HdrMark); + space(kCarrierAc128InterSpace); + // Second part of the message + // Headers + Data + SectionGap + sendGeneric(kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, + data + (nbytes / 2), nbytes / 2, kCarrierAcFreq, + false, 0, kDutyDefault); + // Footer + mark(kCarrierAc128HdrMark); + space(kDefaultMessageGap); + } +} +#endif // SEND_CARRIER_AC128 + +#if DECODE_CARRIER_AC128 +/// Decode the supplied Carrier 128-bit HVAC message. +/// Status: STABLE / Expected to work. +/// @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. +bool IRrecv::decodeCarrierAC128(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * (nbits + 2 * kHeader + kFooter) - 1 + offset) + return false; // Can't possibly be a valid Carrier message. + if (strict && nbits != kCarrierAc128Bits) + return false; // We expect Carrier to be 128 bits of message. + + uint16_t used; + uint16_t pos = 0; + const uint16_t sectionbits = nbits / 2; + // Match the first section. + used = matchGeneric(results->rawbuf + offset, results->state, + results->rawlen - offset, sectionbits, + kCarrierAc128HdrMark, kCarrierAc128HdrSpace, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, true, + kUseDefTol, kMarkExcess, false); + if (used == 0) return false; // No match. + offset += used; + pos += sectionbits / 8; + // Look for the inter-message markers. + if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark)) + return false; + if (!matchSpace(results->rawbuf[offset++], kCarrierAc128InterSpace)) + return false; + // Now look for the second section. + used = matchGeneric(results->rawbuf + offset, results->state + pos, + results->rawlen - offset, sectionbits, + kCarrierAc128Hdr2Mark, kCarrierAc128Hdr2Space, + kCarrierAc128BitMark, kCarrierAc128OneSpace, + kCarrierAc128BitMark, kCarrierAc128ZeroSpace, + kCarrierAc128BitMark, kCarrierAc128SectionGap, true, + kUseDefTol, kMarkExcess, false); + if (used == 0) return false; // No match. + offset += used; + // Now check for the Footer. + if (!matchMark(results->rawbuf[offset++], kCarrierAc128HdrMark)) return false; + if (offset < results->rawlen && + !matchAtLeast(results->rawbuf[offset], kDefaultMessageGap)) return false; + + + // Compliance + // if (strict && !IRCarrierAc128::validChecksum(results->value)) return false; + + // Success + results->bits = nbits; + results->decode_type = CARRIER_AC128; + return true; +} +#endif // DECODE_CARRIER_AC128 diff --git a/src/ir_Carrier.h b/src/ir_Carrier.h index aa9ea8447..c642c51e5 100644 --- a/src/ir_Carrier.h +++ b/src/ir_Carrier.h @@ -1,8 +1,9 @@ -// Copyright 2020 David Conran +// Copyright 2020-2022 David Conran /// @file /// @brief Carrier A/C /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1127 /// @see https://docs.google.com/spreadsheets/d/1EZy78L0cn1KDIX1aKq2biptejFqCjD5HO3tLiRvXf48/edit#gid=0 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1797 // Supports: // Brand: Carrier/Surrey, Model: 42QG5A55970 remote @@ -11,6 +12,7 @@ // Brand: Carrier/Surrey, Model: 619EGX0180E0 A/C // Brand: Carrier/Surrey, Model: 619EGX0220E0 A/C // Brand: Carrier/Surrey, Model: 53NGK009/012 Inverter +// Brand: Carrier, Model: 40GKX0E2006 remote (CARRIER_AC128) #ifndef IR_CARRIER_H_ #define IR_CARRIER_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index a623039f0..876f9145b 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -718,6 +718,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_CARRIER_AC64 #define D_STR_CARRIER_AC64 D_STR_CARRIER_AC "64" #endif // D_STR_CARRIER_AC64 +#ifndef D_STR_CARRIER_AC128 +#define D_STR_CARRIER_AC128 D_STR_CARRIER_AC "128" +#endif // D_STR_CARRIER_AC128 #ifndef D_STR_COOLIX #define D_STR_COOLIX "COOLIX" #endif // D_STR_COOLIX diff --git a/test/ir_Carrier_test.cpp b/test/ir_Carrier_test.cpp index caf7e46d8..fa0f07b0c 100644 --- a/test/ir_Carrier_test.cpp +++ b/test/ir_Carrier_test.cpp @@ -1,8 +1,9 @@ -// Copyright 2018, 2020 David Conran +// Copyright 2018-2022 David Conran #include "ir_Carrier.h" #include "IRac.h" #include "IRrecv.h" +#include "IRrecv_test.h" #include "IRsend.h" #include "IRsend_test.h" #include "gtest/gtest.h" @@ -11,7 +12,7 @@ // Test sending typical data only. TEST(TestSendCarrierAC, SendDataOnly) { - IRsendTest irsend(0); + IRsendTest irsend(kGpioUnused); irsend.begin(); irsend.reset(); @@ -88,7 +89,7 @@ TEST(TestSendCarrierAC, SendDataOnly) { // Test sending typical data only. TEST(TestSendCarrierAC, SendWithRepeats) { - IRsendTest irsend(0); + IRsendTest irsend(kGpioUnused); irsend.begin(); irsend.reset(); @@ -156,7 +157,7 @@ TEST(TestSendCarrierAC, SendWithRepeats) { // Decode normal "synthetic" messages. TEST(TestDecodeCarrierAC, NormalDecodeWithStrict) { - IRsendTest irsend(0); + IRsendTest irsend(kGpioUnused); IRrecv irrecv(0); irsend.begin(); @@ -196,7 +197,7 @@ TEST(TestDecodeCarrierAC, NormalDecodeWithStrict) { // Decode a "real" example message. TEST(TestDecodeCarrierAC, RealExamples) { - IRsendTest irsend(0); + IRsendTest irsend(kGpioUnused); IRrecv irrecv(0); irsend.begin(); @@ -262,6 +263,16 @@ TEST(TestUtils, Housekeeping) { IRsend::defaultBits(decode_type_t::CARRIER_AC64)); ASSERT_EQ(kCarrierAc64MinRepeat, IRsend::minRepeats(decode_type_t::CARRIER_AC64)); + + // CARRIER_AC128 + ASSERT_EQ("CARRIER_AC128", typeToString(decode_type_t::CARRIER_AC128)); + ASSERT_EQ(decode_type_t::CARRIER_AC128, strToDecodeType("CARRIER_AC128")); + ASSERT_TRUE(hasACState(decode_type_t::CARRIER_AC128)); + ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::CARRIER_AC128)); + ASSERT_EQ(kCarrierAc128Bits, + IRsend::defaultBits(decode_type_t::CARRIER_AC128)); + ASSERT_EQ(kCarrierAc128MinRepeat, + IRsend::minRepeats(decode_type_t::CARRIER_AC128)); } /// Decode a "real" example message. @@ -610,3 +621,75 @@ TEST(TestCarrierAc64Class, ReconstructKnownState) { "Sleep: On, On Timer: Off, Off Timer: Off", ac.toString()); } + +// Decode a "real" Carrier 128bit example message. +TEST(TestDecodeCarrierAC128, RealExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + const uint8_t expected_state[kCarrierAc128StateLength] = { + 0x16, 0x22, 0x48, 0x19, 0x10, 0x10, 0x16, 0x28, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x80}; + irsend.begin(); + + irsend.reset(); + // Data from: + // https://docs.google.com/spreadsheets/d/1lqs1UDAvUauzAyoVRAGhARKPaSk3pgOO7iHFw9M5HJE/edit#gid=0&range=F3 + const uint16_t rawData[267] = { + 4594, 2614, + 336, 418, 336, 998, 330, 1002, 338, 416, 336, 998, 332, 424, 328, 426, + 336, 416, 336, 418, 334, 1000, 332, 420, 330, 424, 338, 418, 336, 994, + 334, 420, 332, 424, 328, 424, 338, 416, 336, 418, 336, 996, 332, 422, 330, + 424, 338, 992, 338, 416, 336, 998, 332, 424, 336, 418, 336, 994, 336, 998, + 332, 420, 340, 416, 338, 416, 334, 420, 334, 420, 332, 424, 328, 426, 336, + 994, 336, 418, 334, 422, 330, 422, 332, 422, 338, 416, 338, 416, 336, 418, + 332, 1000, 332, 424, 336, 416, 338, 418, 334, 420, 332, 1000, 332, 1002, + 336, 416, 338, 994, 334, 422, 330, 424, 338, 416, 336, 416, 336, 420, 332, + 422, 330, 1000, 342, 412, 338, 996, 334, 422, 332, 402, + 338, 20636, + 4604, 6724, + 9296, 4976, + 356, 426, 336, 418, 334, 420, 332, 420, 330, 424, 340, 414, 338, 416, 336, + 420, 334, 418, 334, 420, 332, 422, 340, 416, 336, 418, 336, 416, 336, 420, + 332, 420, 332, 424, 338, 416, 336, 418, 336, 994, 334, 422, 332, 422, 340, + 412, 338, 418, 334, 418, 334, 420, 332, 422, 332, 424, 340, 414, 338, 414, + 336, 420, 334, 420, 330, 424, 328, 426, 336, 416, 336, 418, 334, 420, 366, + 388, 362, 392, 358, 392, 360, 394, 368, 386, 368, 388, 364, 388, 366, 390, + 360, 394, 358, 394, 368, 388, 366, 386, 366, 390, 362, 392, 358, 394, 360, + 394, 370, 384, 366, 388, 362, 390, 362, 390, 362, 392, 360, 396, 368, 384, + 366, 390, 364, 390, 362, 392, 360, 954, + 336, 20638, + 4622}; + + irsend.sendRaw(rawData, 267, 38000); + irsend.makeDecodeResult(); + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(CARRIER_AC128, irsend.capture.decode_type); + EXPECT_EQ(kCarrierAc128Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expected_state, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "", + IRAcUtils::resultAcToString(&irsend.capture)); +} + +// Decode a synthetic Carrier AC 128-bit message. +TEST(TestDecodeCarrierAC128, SyntheticExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + const uint8_t expected_state[kCarrierAc128StateLength] = { + 0x16, 0x22, 0x48, 0x19, 0x10, 0x10, 0x16, 0x28, + 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x80}; + irsend.reset(); + irsend.sendCarrierAC128(expected_state); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(CARRIER_AC128, irsend.capture.decode_type); + ASSERT_EQ(kCarrierAc128Bits, irsend.capture.bits); + EXPECT_STATE_EQ(expected_state, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t r, p; + ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); +}