Skip to content

Commit

Permalink
Add basic support for COOLIX48 protocol. (#1697)
Browse files Browse the repository at this point in the history
* As far as we can tell COOLIX48 is a subtle version of `COOLIX` but does not obey the "every second byte is inverted" verification pattern. Thus, COOLIX48 is twice the bit size of COOLIX (24 bits).
* This seems to be used by a Bosch A/C to transmite On & Off timer values.
* Update & Extend Coolix Unit tests to cover this.
* Other minor code style changes.

Fixes #1694
  • Loading branch information
crankyoldgit authored Dec 7, 2021
1 parent fb6cd13 commit 81da75d
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 16 deletions.
8 changes: 6 additions & 2 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,9 +695,9 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
if (decodeSharp(results, offset)) return true;
#endif
#if DECODE_COOLIX
DPRINTLN("Attempting Coolix decode");
DPRINTLN("Attempting Coolix 24-bit decode");
if (decodeCOOLIX(results, offset)) return true;
#endif
#endif // DECODE_COOLIX
#if DECODE_NIKAI
DPRINTLN("Attempting Nikai decode");
if (decodeNikai(results, offset)) return true;
Expand Down Expand Up @@ -1047,6 +1047,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting Airton decode");
if (decodeAirton(results, offset)) return true;
#endif // DECODE_AIRTON
#if DECODE_COOLIX48
DPRINTLN("Attempting Coolix 48-bit decode");
if (decodeCoolix48(results, offset)) return true;
#endif // DECODE_COOLIX48
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
7 changes: 6 additions & 1 deletion src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,12 @@ class IRrecv {
bool decodeCOOLIX(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCoolixBits,
const bool strict = true);
#endif
#endif // DECODE_COOLIX
#if DECODE_COOLIX48
bool decodeCoolix48(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kCoolix48Bits,
const bool strict = true);
#endif // DECODE_COOLIX48
#if DECODE_DENON
bool decodeDenon(decode_results *results, uint16_t offset = kStartOffset,
const uint16_t nbits = kDenonBits,
Expand Down
11 changes: 10 additions & 1 deletion src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,13 @@
#define SEND_COOLIX _IR_ENABLE_DEFAULT_
#endif // SEND_COOLIX

#ifndef DECODE_COOLIX48
#define DECODE_COOLIX48 _IR_ENABLE_DEFAULT_
#endif // DECODE_COOLIX48
#ifndef SEND_COOLIX48
#define SEND_COOLIX48 _IR_ENABLE_DEFAULT_
#endif // SEND_COOLIX48

#ifndef DECODE_GLOBALCACHE
#define DECODE_GLOBALCACHE false // Not applicable.
#endif // DECODE_GLOBALCACHE
Expand Down Expand Up @@ -975,8 +982,9 @@ enum decode_type_t {
ARRIS,
RHOSS,
AIRTON,
COOLIX48, // 110
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = AIRTON,
kLastDecodeType = COOLIX48,
};

// Message lengths & required repeat values
Expand All @@ -998,6 +1006,7 @@ const uint16_t kArgoBits = kArgoStateLength * 8;
const uint16_t kArgoDefaultRepeat = kNoRepeat;
const uint16_t kArrisBits = 32;
const uint16_t kCoolixBits = 24;
const uint16_t kCoolix48Bits = kCoolixBits * 2;
const uint16_t kCoolixDefaultRepeat = kSingleRepeat;
const uint16_t kCarrierAcBits = 32;
const uint16_t kCarrierAcMinRepeat = kNoRepeat;
Expand Down
9 changes: 8 additions & 1 deletion src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ uint16_t IRsend::minRepeats(const decode_type_t protocol) {
case AIWA_RC_T501:
case AMCOR:
case COOLIX:
case COOLIX48:
case ELITESCREENS:
case GICABLE:
case INAX:
Expand Down Expand Up @@ -661,6 +662,7 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kDoshishaBits; // 40
case SANYO_LC7461:
return kSanyoLC7461Bits; // 42
case COOLIX48:
case GOODWEATHER:
case KELON:
case MIDEA:
Expand Down Expand Up @@ -829,7 +831,12 @@ bool IRsend::send(const decode_type_t type, const uint64_t data,
case COOLIX:
sendCOOLIX(data, nbits, min_repeat);
break;
#endif
#endif // SEND_COOLIX
#if SEND_COOLIX48
case COOLIX48:
sendCoolix48(data, nbits, min_repeat);
break;
#endif // SEND_COOLIX48
#if SEND_DAIKIN64
case DAIKIN64:
sendDaikin64(data, nbits, min_repeat);
Expand Down
10 changes: 7 additions & 3 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,13 @@ class IRsend {
uint16_t repeat = kNoRepeat);
#endif
#if SEND_COOLIX
void sendCOOLIX(uint64_t data, uint16_t nbits = kCoolixBits,
uint16_t repeat = kCoolixDefaultRepeat);
#endif
void sendCOOLIX(const uint64_t data, const uint16_t nbits = kCoolixBits,
const uint16_t repeat = kCoolixDefaultRepeat);
#endif // SEND_COOLIX
#if SEND_COOLIX48
void sendCoolix48(const uint64_t data, const uint16_t nbits = kCoolix48Bits,
const uint16_t repeat = kCoolixDefaultRepeat);
#endif // SEND_COOLIX48
#if SEND_WHYNTER
void sendWhynter(const uint64_t data, const uint16_t nbits = kWhynterBits,
const uint16_t repeat = kNoRepeat);
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_ARRIS "\x0"
D_STR_RHOSS "\x0"
D_STR_AIRTON "\x0"
D_STR_COOLIX48 "\x0"
///< New protocol strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
Expand Down
72 changes: 64 additions & 8 deletions src/ir_Coolix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@
// pulse parameters in usec
const uint16_t kCoolixTick = 276; // Approximately 10.5 cycles at 38kHz
const uint16_t kCoolixBitMarkTicks = 2;
const uint16_t kCoolixBitMark = kCoolixBitMarkTicks * kCoolixTick;
const uint16_t kCoolixBitMark = kCoolixBitMarkTicks * kCoolixTick; // 552us
const uint16_t kCoolixOneSpaceTicks = 6;
const uint16_t kCoolixOneSpace = kCoolixOneSpaceTicks * kCoolixTick;
const uint16_t kCoolixOneSpace = kCoolixOneSpaceTicks * kCoolixTick; // 1656us
const uint16_t kCoolixZeroSpaceTicks = 2;
const uint16_t kCoolixZeroSpace = kCoolixZeroSpaceTicks * kCoolixTick;
const uint16_t kCoolixZeroSpace = kCoolixZeroSpaceTicks * kCoolixTick; // 552us
const uint16_t kCoolixHdrMarkTicks = 17;
const uint16_t kCoolixHdrMark = kCoolixHdrMarkTicks * kCoolixTick;
const uint16_t kCoolixHdrMark = kCoolixHdrMarkTicks * kCoolixTick; // 4692us
const uint16_t kCoolixHdrSpaceTicks = 16;
const uint16_t kCoolixHdrSpace = kCoolixHdrSpaceTicks * kCoolixTick;
const uint16_t kCoolixHdrSpace = kCoolixHdrSpaceTicks * kCoolixTick; // 4416us
const uint16_t kCoolixMinGapTicks = kCoolixHdrMarkTicks + kCoolixZeroSpaceTicks;
const uint16_t kCoolixMinGap = kCoolixMinGapTicks * kCoolixTick;
const uint16_t kCoolixMinGap = kCoolixMinGapTicks * kCoolixTick; // 5244us
const uint8_t kCoolix48ExtraTolerance = 5; // Percent

using irutils::addBoolToString;
using irutils::addIntToString;
Expand All @@ -40,7 +41,7 @@ using irutils::addModeToString;
using irutils::addTempToString;

#if SEND_COOLIX
/// Send a Coolix message
/// Send a Coolix 24-bit message
/// Status: STABLE / Confirmed Working.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
Expand Down Expand Up @@ -620,7 +621,7 @@ String IRCoolixAC::toString(void) const {
}

#if DECODE_COOLIX
/// Decode the supplied Coolix A/C message.
/// Decode the supplied Coolix 24-bit A/C message.
/// Status: STABLE / Known Working.
/// @param[in,out] results Ptr to the data to decode & where to store the decode
/// result.
Expand Down Expand Up @@ -699,3 +700,58 @@ bool IRrecv::decodeCOOLIX(decode_results *results, uint16_t offset,
return true;
}
#endif // DECODE_COOLIX

#if SEND_COOLIX48
/// Send a Coolix 48-bit message.
/// Status: ALPHA / Untested.
/// @param[in] data The message to be sent.
/// @param[in] nbits The number of bits of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694
/// @note This is effectively the same as `sendCOOLIX()` except requiring the
/// bit flipping be done prior to the call.
void IRsend::sendCoolix48(const uint64_t data, const uint16_t nbits,
const uint16_t repeat) {
// Header + Data + Footer
sendGeneric(kCoolixHdrMark, kCoolixHdrSpace,
kCoolixBitMark, kCoolixOneSpace,
kCoolixBitMark, kCoolixZeroSpace,
kCoolixBitMark, kCoolixMinGap,
data, nbits, 38000, true, repeat, 33);
}
#endif // SEND_COOLIX48

#if DECODE_COOLIX
/// Decode the supplied Coolix 48-bit A/C message.
/// Status: BETA / Probably 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/1694
bool IRrecv::decodeCoolix48(decode_results *results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (strict && nbits != kCoolix48Bits)
return false; // Not strictly a COOLIX48 message.

// Header + Data + Footer
if (!matchGeneric(results->rawbuf + offset, &(results->value),
results->rawlen - offset, nbits,
kCoolixHdrMark, kCoolixHdrSpace,
kCoolixBitMark, kCoolixOneSpace,
kCoolixBitMark, kCoolixZeroSpace,
kCoolixBitMark, kCoolixMinGap,
true, _tolerance + kCoolix48ExtraTolerance, 0, true))
return false;

// Success
results->decode_type = COOLIX48;
results->bits = nbits;
results->address = 0;
results->command = 0;
return true;
}
#endif // DECODE_COOLIX48
4 changes: 4 additions & 0 deletions src/ir_Coolix.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
/// @note Kudos:
/// Hamper: For the breakdown and mapping of the bit values.
/// fraschizzato: For additional ZoneFollow & SwingVStep analysis.
/// @note Timers seem to use the `COOLIX48` protocol.
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1694

// Supports:
// Brand: Beko, Model: RG57K7(B)/BGEF Remote
Expand All @@ -21,6 +23,8 @@
// Brand: Toshiba, Model: RAS-M13YKV-E A/C
// Brand: Toshiba, Model: RAS-4M27YAV-E A/C
// Brand: Toshiba, Model: WH-E1YE remote
// Brand: Bosch, Model: RG36B4/BGE remote
// Brand: Bosch, Model: B1ZAI2441W/B1ZAO2441W A/C

#ifndef IR_COOLIX_H_
#define IR_COOLIX_H_
Expand Down
3 changes: 3 additions & 0 deletions src/locale/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,9 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_COOLIX
#define D_STR_COOLIX "COOLIX"
#endif // D_STR_COOLIX
#ifndef D_STR_COOLIX48
#define D_STR_COOLIX48 D_STR_COOLIX "48"
#endif // D_STR_COOLIX48
#ifndef D_STR_CORONA_AC
#define D_STR_CORONA_AC "CORONA_AC"
#endif // D_STR_CORONA_AC
Expand Down
92 changes: 92 additions & 0 deletions test/ir_Coolix_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@
#include "IRsend_test.h"
#include "gtest/gtest.h"


TEST(TestUtils, Housekeeping) {
// COOLIX
ASSERT_EQ("COOLIX", typeToString(decode_type_t::COOLIX));
ASSERT_EQ(decode_type_t::COOLIX, strToDecodeType("COOLIX"));
ASSERT_FALSE(hasACState(decode_type_t::COOLIX));
ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::COOLIX));
ASSERT_EQ(kCoolixBits, IRsend::defaultBits(decode_type_t::COOLIX));
ASSERT_EQ(kSingleRepeat, IRsend::minRepeats(decode_type_t::COOLIX));

// COOLIX48
ASSERT_EQ("COOLIX48", typeToString(decode_type_t::COOLIX48));
ASSERT_EQ(decode_type_t::COOLIX48, strToDecodeType("COOLIX48"));
ASSERT_FALSE(hasACState(decode_type_t::COOLIX48));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::COOLIX48));
ASSERT_EQ(kCoolix48Bits, IRsend::defaultBits(decode_type_t::COOLIX48));
ASSERT_EQ(kSingleRepeat, IRsend::minRepeats(decode_type_t::COOLIX48));
}

// Tests for sendCOOLIX().

// Test sending typical data only.
Expand Down Expand Up @@ -941,3 +960,76 @@ TEST(TestCoolixACClass, VerifyZoneFollowFan) {
"Zone Follow: On, Sensor Temp: 19C",
ac.toString());
}

TEST(TestDecodeCoolix48, RealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);

// Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1694#issue-1068786691
// Off Timer: 1 hour
const uint16_t rawData[199] = {
4342, 4454, 486, 1724, 436, 658, 438, 1748, 464, 1718, 462, 634, 440, 656,
462, 1696, 488, 634, 462, 634, 436, 1722, 516, 608, 462, 660, 436, 1694,
488, 1720, 440, 630, 488, 1700, 488, 1704, 458, 660, 462, 1698, 490, 632,
462, 634, 436, 684, 436, 1700, 464, 1748, 462, 634, 462, 1720, 436, 658,
462, 1700, 488, 1692, 512, 1696, 438, 684, 410, 686, 434, 688, 408, 1696,
488, 1694, 464, 682, 414, 1748, 436, 1722, 488, 632, 438, 686, 408, 662,
462, 1696, 488, 1722, 462, 1696, 462, 1746, 436, 1798, 386, 1694, 490,
1720, 516, 5234, 4370, 4446, 490, 1690, 492, 658, 434, 1726, 436, 1746,
464, 604, 488, 658, 412, 1718, 490, 636, 460, 660, 438, 1698, 460, 662,
458, 632, 436, 1718, 490, 1720, 488, 608, 436, 1754, 462, 1726, 438, 682,
414, 1748, 464, 632, 460, 660, 410, 658, 438, 1748, 464, 1694, 464, 660,
436, 1720, 488, 634, 460, 1726, 462, 1724, 462, 1692, 490, 606, 462, 714,
384, 660, 460, 1722, 460, 1722, 490, 606, 464, 1718, 490, 1670, 486, 634,
462, 662, 410, 660, 460, 1722, 464, 1718, 460, 1696, 464, 1720, 462, 1720,
462, 1722, 486, 1700, 462}; // UNKNOWN 1F691B97

irsend.begin();
irsend.reset();

irsend.sendRaw(rawData, 199, 38000);
irsend.makeDecodeResult();
ASSERT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(COOLIX48, irsend.capture.decode_type);
EXPECT_EQ(kCoolix48Bits, irsend.capture.bits);
EXPECT_EQ(0xB24DA35C6C7F, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);
}

TEST(TestDecodeCoolix48, SyntheticSelfDecode) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

irsend.reset();
irsend.sendCoolix48(0xB24DA35C6C7F);
irsend.makeDecodeResult();

ASSERT_TRUE(irrecv.decode(&irsend.capture));
EXPECT_EQ(COOLIX48, irsend.capture.decode_type);
EXPECT_EQ(kCoolix48Bits, irsend.capture.bits);
EXPECT_EQ(0xB24DA35C6C7F, irsend.capture.value);
EXPECT_EQ(0x0, irsend.capture.address);
EXPECT_EQ(0x0, irsend.capture.command);

EXPECT_EQ(
"f38000d33"
"m4692s4416" // Message.
"m552s1656m552s552m552s1656m552s1656m552s552m552s552m552s1656m552s552"
"m552s552m552s1656m552s552m552s552m552s1656m552s1656m552s552m552s1656"
"m552s1656m552s552m552s1656m552s552m552s552m552s552m552s1656m552s1656"
"m552s552m552s1656m552s552m552s1656m552s1656m552s1656m552s552m552s552"
"m552s552m552s1656m552s1656m552s552m552s1656m552s1656m552s552m552s552"
"m552s552m552s1656m552s1656m552s1656m552s1656m552s1656m552s1656m552s1656"
"m552s5244"
"m4692s4416" // Repeat
"m552s1656m552s552m552s1656m552s1656m552s552m552s552m552s1656m552s552"
"m552s552m552s1656m552s552m552s552m552s1656m552s1656m552s552m552s1656"
"m552s1656m552s552m552s1656m552s552m552s552m552s552m552s1656m552s1656"
"m552s552m552s1656m552s552m552s1656m552s1656m552s1656m552s552m552s552"
"m552s552m552s1656m552s1656m552s552m552s1656m552s1656m552s552m552s552"
"m552s552m552s1656m552s1656m552s1656m552s1656m552s1656m552s1656m552s1656"
"m552s5244",
irsend.outputStr());
}

0 comments on commit 81da75d

Please sign in to comment.