Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental basic support for TCL AC 96 bit protocol. #1820

Merged
merged 2 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
91 changes: 89 additions & 2 deletions src/ir_Tcl.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019, 2021 David Conran
// Copyright 2019, 2021, 2022 David Conran

/// @file
/// @brief Support for TCL protocols.
Expand All @@ -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
2182, // 0b10
1444}; // 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
70 changes: 69 additions & 1 deletion test/ir_Tcl_test.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 David Conran
// Copyright 2019-2022 David Conran

#include "ir_Tcl.h"
#include "IRac.h"
Expand All @@ -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] = {
0xB6, 0x23, 0x00, 0x10, 0x85, 0x09, 0x63, 0x34, 0x00, 0x04, 0x00, 0x5A};

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] = {
0xB6, 0x23, 0x00, 0x10, 0x85, 0x09, 0x63, 0x34, 0x00, 0x04, 0x00, 0x5A};

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));
}