Skip to content

Commit

Permalink
Experimental basic support for TCL AC 96 bit protocol.
Browse files Browse the repository at this point in the history
* NOTE: This is a 2-bit per mark/space pair protocol.
* Add `sendTcl96Ac()` & `decodeTcl96Ac()` routines.
* Unit test coverage.
* Fix mistake with `DECODDE_AC` define.

This is highly experimental. Bit ordering etc is a complete guess at this stage.

For #1810
  • Loading branch information
crankyoldgit committed Jun 4, 2022
1 parent c011761 commit b346622
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 4 deletions.
4 changes: 4 additions & 0 deletions src/IRrecv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,10 @@ bool IRrecv::decode(decode_results *results, irparams_t *save,
DPRINTLN("Attempting ClimaButler decode");
if (decodeClimaButler(results)) return true;
#endif // DECODE_CLIMABUTLER
#if DECODE_TCL96AC
DPRINTLN("Attempting TCL AC 96-bit decode");
if (decodeTcl96Ac(results, offset)) return true;
#endif // DECODE_TCL96AC
// Typically new protocols are added above this line.
}
#if DECODE_HASH
Expand Down
6 changes: 6 additions & 0 deletions src/IRrecv.h
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,12 @@ class IRrecv {
const uint16_t nbits = kClimaButlerBits,
const bool strict = true);
#endif // DECODE_CLIMABUTLER
#if DECODE_TCL96AC
bool decodeTcl96Ac(decode_results *results,
uint16_t offset = kStartOffset,
const uint16_t nbits = kTcl96AcBits,
const bool strict = true);
#endif // DECODE_TCL96AC
};

#endif // IRRECV_H_
15 changes: 13 additions & 2 deletions src/IRremoteESP8266.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,13 @@
#define SEND_TECO _IR_ENABLE_DEFAULT_
#endif // SEND_TECO

#ifndef DECODE_TCL96AC
#define DECODE_TCL96AC _IR_ENABLE_DEFAULT_
#endif // DECODE_TCL96AC
#ifndef SEND_TCL96AC
#define SEND_TCL96AC _IR_ENABLE_DEFAULT_
#endif // SEND_TCL96AC

#ifndef DECODE_TCL112AC
#define DECODE_TCL112AC _IR_ENABLE_DEFAULT_
#endif // DECODE_TCL112AC
Expand Down Expand Up @@ -912,7 +919,7 @@
DECODE_TEKNOPOINT || DECODE_KELON || DECODE_TROTEC_3550 || \
DECODE_SANYO_AC88 || DECODE_RHOSS || DECODE_HITACHI_AC264 || \
DECODE_KELON168 || DECODE_HITACHI_AC296 || DECODE_CARRIER_AC128 || \
DECODE_DAIKIN200 || SEND_HAIER_AC160 || \
DECODE_DAIKIN200 || DECODE_HAIER_AC160 || DECODE_TCL96AC || \
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
Expand Down Expand Up @@ -1071,8 +1078,9 @@ enum decode_type_t {
CARRIER_AC128,
TOTO,
CLIMABUTLER,
TCL96AC,
// Add new entries before this one, and update it to point to the last entry.
kLastDecodeType = CLIMABUTLER,
kLastDecodeType = TCL96AC,
};

// Message lengths & required repeat values
Expand Down Expand Up @@ -1300,6 +1308,9 @@ const uint16_t kSonyMinBits = 12;
const uint16_t kSonyMinRepeat = 2;
const uint16_t kSymphonyBits = 12;
const uint16_t kSymphonyDefaultRepeat = 3;
const uint16_t kTcl96AcStateLength = 12;
const uint16_t kTcl96AcBits = kTcl96AcStateLength * 8;
const uint16_t kTcl96AcDefaultRepeat = kNoRepeat;
const uint16_t kTcl112AcStateLength = 14;
const uint16_t kTcl112AcBits = kTcl112AcStateLength * 8;
const uint16_t kTcl112AcDefaultRepeat = kNoRepeat;
Expand Down
7 changes: 7 additions & 0 deletions src/IRsend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,8 @@ uint16_t IRsend::defaultBits(const decode_type_t protocol) {
return kSanyoAc88Bits;
case SHARP_AC:
return kSharpAcBits;
case TCL96AC:
return kTcl96AcBits;
case TCL112AC:
return kTcl112AcBits;
case TEKNOPOINT:
Expand Down Expand Up @@ -1349,6 +1351,11 @@ bool IRsend::send(const decode_type_t type, const uint8_t *state,
sendSharpAc(state, nbytes);
break;
#endif // SEND_SHARP_AC
#if SEND_TCL96AC
case TCL96AC:
sendTcl96Ac(state, nbytes);
break;
#endif // SEND_TCL96AC
#if SEND_TCL112AC
case TCL112AC:
sendTcl112Ac(state, nbytes);
Expand Down
7 changes: 6 additions & 1 deletion src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -670,11 +670,16 @@ class IRsend {
void sendVestelAc(const uint64_t data, const uint16_t nbits = kVestelAcBits,
const uint16_t repeat = kNoRepeat);
#endif
#if SEND_TCL96AC
void sendTcl96Ac(const unsigned char data[],
const uint16_t nbytes = kTcl96AcStateLength,
const uint16_t repeat = kTcl96AcDefaultRepeat);
#endif // SEND_TCL96AC
#if SEND_TCL112AC
void sendTcl112Ac(const unsigned char data[],
const uint16_t nbytes = kTcl112AcStateLength,
const uint16_t repeat = kTcl112AcDefaultRepeat);
#endif
#endif // SEND_TCL112AC
#if SEND_TECO
void sendTeco(const uint64_t data, const uint16_t nbits = kTecoBits,
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 @@ -401,6 +401,7 @@ IRTEXT_CONST_BLOB_DECL(kAllProtocolNamesStr) {
D_STR_CARRIER_AC128 "\x0"
D_STR_TOTO "\x0"
D_STR_CLIMABUTLER "\x0"
D_STR_TCL96AC "\x0"
///< New protocol strings should be added just above this line.
"\x0" ///< This string requires double null termination.
};
Expand Down
1 change: 1 addition & 0 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ bool hasACState(const decode_type_t protocol) {
case SANYO_AC:
case SANYO_AC88:
case SHARP_AC:
case TCL96AC:
case TCL112AC:
case TEKNOPOINT:
case TOSHIBA_AC:
Expand Down
89 changes: 88 additions & 1 deletion src/ir_Tcl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,19 @@
#include "IRutils.h"

// Constants

const uint8_t kTcl112AcTimerResolution = 20; // Minutes
const uint16_t kTcl112AcTimerMax = 720; // Minutes (12 hrs)

const uint16_t kTcl96AcHdrMark = 1056; // uSeconds.
const uint16_t kTcl96AcHdrSpace = 550; // uSeconds.
const uint16_t kTcl96AcBitMark = 600; // uSeconds.
const uint32_t kTcl96AcGap = kDefaultMessageGap; // Just a guess.
const uint8_t kTcl96AcSpaceCount = 4;
const uint16_t kTcl96AcBitSpaces[kTcl96AcSpaceCount] = {360, // 0b00
838, // 0b01
1444, // 0b10
2182}; // 0b11

using irutils::addBoolToString;
using irutils::addFanToString;
using irutils::addIntToString;
Expand Down Expand Up @@ -527,3 +536,81 @@ String IRTcl112Ac::toString(void) const {
/// It's the same as `decodeMitsubishi112()`. A shared routine is used.
/// You can find it in: ir_Mitsubishi.cpp
#endif // DECODE_TCL112AC

#if SEND_TCL96AC
/// Send a TCL 96-bit A/C message.
/// Status: BETA / Untested on a real device working.
/// @param[in] data The message to be sent.
/// @param[in] nbytes The number of bytes of message to be sent.
/// @param[in] repeat The number of times the command is to be repeated.
void IRsend::sendTcl96Ac(const unsigned char data[], const uint16_t nbytes,
const uint16_t repeat) {
enableIROut(38);
for (uint16_t r = 0; r <= repeat; r++) {
// Header
mark(kTcl96AcHdrMark);
space(kTcl96AcHdrSpace);
// Data
for (uint16_t pos = 0; pos < nbytes; pos++) {
uint8_t databyte = data[pos];
for (uint8_t bits = 0; bits < 8; bits += 2) {
mark(kTcl96AcBitMark);
space(kTcl96AcBitSpaces[GETBITS8(databyte, 8 - 2, 2)]);
databyte <<= 2;
}
}
// Footer
mark(kTcl96AcBitMark);
space(kTcl96AcGap);
}
}
#endif // SEND_TCL96AC

#if DECODE_TCL96AC
/// Decode the supplied Tcl96Ac message.
/// Status: ALPHA / Experimental.
/// @param[in,out] results Ptr to the data to decode & where to store the 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 True if it can decode it, false if it can't.
bool IRrecv::decodeTcl96Ac(decode_results* results, uint16_t offset,
const uint16_t nbits, const bool strict) {
if (results->rawlen < nbits + kHeader + kFooter - 1 + offset)
return false; // Message is smaller than we expected.
if (strict && nbits != kTcl96AcBits)
return false; // Not strictly a TCL96AC message.
uint8_t data = 0;
// Header.
if (!matchMark(results->rawbuf[offset++], kTcl96AcHdrMark)) return false;
if (!matchSpace(results->rawbuf[offset++], kTcl96AcHdrSpace)) return false;
// Data (2 bits at a time)
for (uint16_t bits_so_far = 0; bits_so_far < nbits; bits_so_far += 2) {
if (bits_so_far % 8)
data <<= 2; // Make space for the new data bits.
else
data = 0;
if (!matchMark(results->rawbuf[offset++], kTcl96AcBitMark)) return false;
uint8_t value = 0;
while (value < kTcl96AcSpaceCount) {
if (matchSpace(results->rawbuf[offset], kTcl96AcBitSpaces[value])) {
data += value;
break;
}
value++;
}
if (value >= kTcl96AcSpaceCount) return false; // No matches.
offset++;
*(results->state + bits_so_far / 8) = data;
}
// Footer
if (!matchMark(results->rawbuf[offset++], kTcl96AcBitMark)) return false;
if (offset < results->rawlen &&
!matchAtLeast(results->rawbuf[offset], kTcl96AcGap)) return false;
// Success
results->decode_type = TCL96AC;
results->bits = nbits;
return true;
}
#endif // DECODE_TCL96AC
1 change: 1 addition & 0 deletions src/ir_Tcl.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// Brand: Teknopoint, Model: GZ-055B-E1 remote (GZ055BE1)
// Brand: Daewoo, Model: DSB-F0934ELH-V A/C
// Brand: Daewoo, Model: GYKQ-52E remote
// Brand: TCL, Model: GYKQ-58(XM) remote (TCL96AC)

#ifndef IR_TCL_H_
#define IR_TCL_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 @@ -997,6 +997,9 @@ D_STR_INDIRECT " " D_STR_MODE
#ifndef D_STR_SYMPHONY
#define D_STR_SYMPHONY "SYMPHONY"
#endif // D_STR_SYMPHONY
#ifndef D_STR_TCL96AC
#define D_STR_TCL96AC "TCL96AC"
#endif // D_STR_TCL96AC
#ifndef D_STR_TCL112AC
#define D_STR_TCL112AC "TCL112AC"
#endif // D_STR_TCL112AC
Expand Down
68 changes: 68 additions & 0 deletions test/ir_Tcl_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ TEST(TestTcl112Ac, Housekeeping) {
ASSERT_EQ(tcl_ac_remote_model_t::GZ055BE1, IRac::strToModel("GZ055BE1"));
ASSERT_EQ(irutils::modelToStr(decode_type_t::TCL112AC,
tcl_ac_remote_model_t::GZ055BE1), "GZ055BE1");

ASSERT_EQ("TCL96AC", typeToString(decode_type_t::TCL96AC));
ASSERT_EQ(decode_type_t::TCL96AC, strToDecodeType("TCL96AC"));
ASSERT_TRUE(hasACState(decode_type_t::TCL96AC));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::TCL96AC));
ASSERT_EQ(kTcl96AcBits, IRsend::defaultBits(decode_type_t::TCL96AC));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::TCL96AC));
}

// Tests for decodeTcl112Ac().
Expand Down Expand Up @@ -703,3 +710,64 @@ TEST(TestTcl112AcClass, Timers) {
"On Timer: Off, Off Timer: 02:00",
ac.toString());
}

// Decode a real Tcl96Ac A/C example from Issue #619
TEST(TestDecodeTcl96Ac, DecodeRealExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();

irsend.reset();
// Tcl96Ac A/C example from Issue #1810 row_data.txt
const uint16_t rawData[99] = {
1056, 550,
608, 2182, 608, 1444, 606, 840, 608, 2182,
608, 360, 612, 2182, 608, 356, 616, 1446,
608, 354, 618, 366, 608, 366, 606, 356,
618, 356, 618, 838, 608, 364, 608, 364,
608, 2182, 608, 360, 612, 840, 608, 838,
610, 364, 608, 360, 612, 2182, 608, 838,
608, 838, 608, 2182, 608, 366, 606, 1444,
608, 358, 614, 1444, 608, 838, 608, 366,
606, 368, 606, 366, 606, 366, 606, 366,
608, 364, 608, 342, 632, 840, 606, 340,
606, 364, 634, 338, 634, 340, 632, 340,
634, 814, 632, 814, 632, 2156, 634, 2156,
634}; // UNKNOWN AE10E0CB

const uint8_t expectedState[kTcl96AcStateLength] = {
0xE7, 0x32, 0x00, 0x10, 0xC5, 0x0D, 0x72, 0x24, 0x00, 0x04, 0x00, 0x5F};

irsend.sendRaw(rawData, 99, 38000);
irsend.makeDecodeResult();

ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(TCL96AC, irsend.capture.decode_type);
EXPECT_EQ(kTcl96AcBits, irsend.capture.bits);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"",
IRAcUtils::resultAcToString(&irsend.capture));
}

// Decode a synthetic Tcl96Ac A/C message
TEST(TestDecodeTcl96Ac, SyntheticExample) {
IRsendTest irsend(kGpioUnused);
IRrecv irrecv(kGpioUnused);
irsend.begin();
irsend.reset();

const uint8_t expectedState[kTcl96AcStateLength] = {
0xE7, 0x32, 0x00, 0x10, 0xC5, 0x0D, 0x72, 0x24, 0x00, 0x04, 0x00, 0x5F};

irsend.sendTcl96Ac(expectedState);
irsend.makeDecodeResult();

ASSERT_TRUE(irrecv.decode(&irsend.capture));
ASSERT_EQ(TCL96AC, irsend.capture.decode_type);
EXPECT_EQ(kTcl96AcBits, irsend.capture.bits);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"",
IRAcUtils::resultAcToString(&irsend.capture));
}

0 comments on commit b346622

Please sign in to comment.