diff --git a/src/IRrecv.cpp b/src/IRrecv.cpp index f6e065eaa..cf7910167 100644 --- a/src/IRrecv.cpp +++ b/src/IRrecv.cpp @@ -1119,6 +1119,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save, if (decodeToto(results, offset, kTotoLongBits) || // Long needs to be first decodeToto(results, offset, kTotoShortBits)) return true; #endif // DECODE_TOTO +#if DECODE_CLIMABUTLER + DPRINTLN("Attempting ClimaButler decode"); + if (decodeClimaButler(results)) return true; +#endif // DECODE_CLIMABUTLER // Typically new protocols are added above this line. } #if DECODE_HASH diff --git a/src/IRrecv.h b/src/IRrecv.h index b22d488fd..479a42baa 100644 --- a/src/IRrecv.h +++ b/src/IRrecv.h @@ -827,6 +827,12 @@ class IRrecv { const uint16_t nbits = kTotoBits, const bool strict = true); #endif // DECODE_TOTO +#if DECODE_CLIMABUTLER + bool decodeClimaButler(decode_results *results, + uint16_t offset = kStartOffset, + const uint16_t nbits = kClimaButlerBits, + const bool strict = true); +#endif // DECODE_CLIMABUTLER }; #endif // IRRECV_H_ diff --git a/src/IRremoteESP8266.h b/src/IRremoteESP8266.h index 027440764..b3074082e 100644 --- a/src/IRremoteESP8266.h +++ b/src/IRremoteESP8266.h @@ -889,6 +889,13 @@ #define SEND_TOTO _IR_ENABLE_DEFAULT_ #endif // SEND_TOTO +#ifndef DECODE_CLIMABUTLER +#define DECODE_CLIMABUTLER _IR_ENABLE_DEFAULT_ +#endif // DECODE_CLIMABUTLER +#ifndef SEND_CLIMABUTLER +#define SEND_CLIMABUTLER _IR_ENABLE_DEFAULT_ +#endif // SEND_CLIMABUTLER + #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 || \ @@ -1063,8 +1070,9 @@ enum decode_type_t { HAIER_AC160, // 115 CARRIER_AC128, TOTO, + CLIMABUTLER, // Add new entries before this one, and update it to point to the last entry. - kLastDecodeType = TOTO, + kLastDecodeType = CLIMABUTLER, }; // Message lengths & required repeat values @@ -1333,6 +1341,7 @@ const uint16_t kBoseBits = 16; const uint16_t kRhossStateLength = 12; const uint16_t kRhossBits = kRhossStateLength * 8; const uint16_t kRhossDefaultRepeat = 0; +const uint16_t kClimaButlerBits = 52; // Legacy defines. (Deprecated) diff --git a/src/IRsend.cpp b/src/IRsend.cpp index 84983e7f2..d43a72125 100644 --- a/src/IRsend.cpp +++ b/src/IRsend.cpp @@ -670,6 +670,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) { case MIDEA: case PANASONIC: return 48; + case CLIMABUTLER: + return kClimaButlerBits; // 52 case AIRTON: case ECOCLIM: case MAGIQUEST: @@ -841,6 +843,11 @@ bool IRsend::send(const decode_type_t type, const uint64_t data, sendCarrierAC64(data, nbits, min_repeat); break; #endif // SEND_CARRIER_AC64 +#if SEND_CLIMABUTLER + case CLIMABUTLER: + sendClimaButler(data, nbits, min_repeat); + break; +#endif // SEND_CLIMABUTLER #if SEND_COOLIX case COOLIX: sendCOOLIX(data, nbits, min_repeat); diff --git a/src/IRsend.h b/src/IRsend.h index ccc07fe23..06da2f5d4 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -810,6 +810,11 @@ class IRsend { void sendToto(const uint64_t data, const uint16_t nbits = kTotoBits, const uint16_t repeat = kTotoDefaultRepeat); #endif // SEND_TOTO +#if SEND_CLIMABUTLER + void sendClimaButler(const uint64_t data, + const uint16_t nbits = kClimaButlerBits, + const uint16_t repeat = kNoRepeat); +#endif // SEND_CLIMABUTLER protected: #ifdef UNIT_TEST diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 0f681922b..960f94c5e 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -400,6 +400,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) { D_STR_HAIER_AC160 "\x0" D_STR_CARRIER_AC128 "\x0" D_STR_TOTO "\x0" + D_STR_CLIMABUTLER "\x0" ///< New protocol strings should be added just above this line. "\x0" ///< This string requires double null termination. }; diff --git a/src/ir_ClimaButler.cpp b/src/ir_ClimaButler.cpp new file mode 100644 index 000000000..d8b4f5a98 --- /dev/null +++ b/src/ir_ClimaButler.cpp @@ -0,0 +1,86 @@ +// Copyright 2022 benjy3gg +// Copyright 2022 David Conran (crankyoldgit) +/// @file +/// @brief Support for Clima-Butler protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1812 + +// Supports: +// Brand: Clima-Butler, Model: AR-715 remote +// Brand: Clima-Butler, Model: RCS-SD43UWI A/C + +#include "IRrecv.h" +#include "IRsend.h" +#include "IRutils.h" + + +const uint16_t kClimaButlerBitMark = 511; // uSeconds +const uint16_t kClimaButlerHdrMark = kClimaButlerBitMark; +const uint16_t kClimaButlerHdrSpace = 3492; // uSeconds +const uint16_t kClimaButlerOneSpace = 1540; // uSeconds +const uint16_t kClimaButlerZeroSpace = 548; // uSeconds +const uint32_t kClimaButlerGap = kDefaultMessageGap; // uSeconds (A guess.) +const uint16_t kClimaButlerFreq = 38000; // Hz. (Guess.) + +#if SEND_CLIMABUTLER +/// Send a ClimaButler formatted message. +/// Status: STABLE / Confirmed working. +/// @param[in] data containing the IR command. +/// @param[in] nbits Nr. of bits to send. usually kClimaButlerBits +/// @param[in] repeat Nr. of times the message is to be repeated. +void IRsend::sendClimaButler(const uint64_t data, const uint16_t nbits, + const uint16_t repeat) { + enableIROut(kClimaButlerFreq); + for (uint16_t r = 0; r <= repeat; r++) { + // Header + Data + sendGeneric(kClimaButlerHdrMark, kClimaButlerHdrSpace, + kClimaButlerBitMark, kClimaButlerOneSpace, + kClimaButlerBitMark, kClimaButlerZeroSpace, + kClimaButlerBitMark, kClimaButlerHdrSpace, + data, nbits, kClimaButlerFreq, true, 0, kDutyDefault); + // Footer + mark(kClimaButlerBitMark); + space(kClimaButlerGap); + } +} +#endif // SEND_CLIMABUTLER + +#if DECODE_CLIMABUTLER +/// Decode the supplied ClimaButler message. +/// Status: STABLE / Confirmed working. +/// @param[in,out] results Ptr to the data to decode & where to store the decode +/// @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::decodeClimaButler(decode_results *results, uint16_t offset, + const uint16_t nbits, const bool strict) { + if (results->rawlen < 2 * nbits + kHeader + 2 * kFooter - offset) + return false; // Too short a message to match. + if (strict && nbits != kClimaButlerBits) + return false; + + // Header + Data + uint16_t used = matchGeneric(results->rawbuf + offset, &(results->value), + results->rawlen - offset, nbits, + kClimaButlerHdrMark, kClimaButlerHdrSpace, + kClimaButlerBitMark, kClimaButlerOneSpace, + kClimaButlerBitMark, kClimaButlerZeroSpace, + kClimaButlerBitMark, kClimaButlerHdrSpace); + if (!used) return false; // Didn't matched. + offset += used; + // Footer + if (!matchMark(results->rawbuf[offset++], kClimaButlerBitMark)) + return false; + if (results->rawlen <= offset && !matchAtLeast(results->rawbuf[offset], + kClimaButlerGap)) + return false; + + // Success + results->decode_type = decode_type_t::CLIMABUTLER; + results->bits = nbits; + results->command = 0; + results->address = 0; + return true; +} +#endif // DECODE_CLIMABUTLER diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 700673707..622acfb84 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -721,6 +721,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_CARRIER_AC128 #define D_STR_CARRIER_AC128 D_STR_CARRIER_AC "128" #endif // D_STR_CARRIER_AC128 +#ifndef D_STR_CLIMABUTLER +#define D_STR_CLIMABUTLER "CLIMABUTLER" +#endif // D_STR_CLIMABUTLER #ifndef D_STR_COOLIX #define D_STR_COOLIX "COOLIX" #endif // D_STR_COOLIX diff --git a/test/ir_ClimaButler_test.cpp b/test/ir_ClimaButler_test.cpp new file mode 100644 index 000000000..6a9841d27 --- /dev/null +++ b/test/ir_ClimaButler_test.cpp @@ -0,0 +1,67 @@ +// Copyright 2022 crankyoldgit + +#include "IRac.h" +#include "IRrecv.h" +#include "IRrecv_test.h" +#include "IRsend.h" +#include "IRsend_test.h" +#include "gtest/gtest.h" + +// Tests for decodeClimaButler(). +TEST(TestDecodeClimaButler, RealExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + const uint16_t rawData[109] = { + 554, 3512, + 558, 1488, 580, 496, 522, 494, 546, 558, 552, 494, 548, 496, 546, 500, + 548, 558, 544, 504, 544, 502, 540, 506, 538, 570, 514, 530, 514, 532, 512, + 534, 512, 592, 512, 536, 510, 532, 510, 536, 510, 598, 510, 536, 506, 536, + 514, 534, 510, 594, 514, 534, 510, 536, 534, 510, 510, 598, 514, 534, 510, + 536, 508, 536, 534, 572, 534, 500, 512, 1536, 512, 526, 510, 1588, 512, + 1536, 510, 538, 512, 532, 510, 588, 510, 1536, 514, 532, 510, 536, 534, + 570, 512, 534, 510, 536, 514, 528, 540, 568, 540, 506, 512, 524, 510, + 1534, 516, 532, + 536, 3396, + 544}; // UNKNOWN E6CA5369 POWER OFF + irsend.begin(); + irsend.reset(); + irsend.sendRaw(rawData, 109, 38); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(decode_type_t::CLIMABUTLER, irsend.capture.decode_type); + ASSERT_EQ(kClimaButlerBits, irsend.capture.bits); + EXPECT_EQ(0x8000000058802, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t r, p; + ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); +} + +TEST(TestDecodeClimaButler, SyntheticExample) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + irsend.reset(); + irsend.sendClimaButler(0x8000000058802); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(decode_type_t::CLIMABUTLER, irsend.capture.decode_type); + ASSERT_EQ(kClimaButlerBits, irsend.capture.bits); + EXPECT_EQ(0x8000000058802, irsend.capture.value); + EXPECT_EQ(0x0, irsend.capture.address); + EXPECT_EQ(0x0, irsend.capture.command); +} + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("CLIMABUTLER", typeToString(decode_type_t::CLIMABUTLER)); + ASSERT_EQ(decode_type_t::CLIMABUTLER, strToDecodeType("CLIMABUTLER")); + ASSERT_FALSE(hasACState(decode_type_t::CLIMABUTLER)); + ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::CLIMABUTLER)); + ASSERT_EQ(kClimaButlerBits, IRsend::defaultBits(decode_type_t::CLIMABUTLER)); + ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::CLIMABUTLER)); +}