From 127befbd6aafb230049d169b0a861133462b1f53 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 10 Jul 2021 00:18:43 +1000 Subject: [PATCH] TCL112AC: Add support for quiet/mute setting. * Changes required to handle quiet setting, which is sent in a separate special message. * Modify checksum alg. for these special messages. * Add `IRac` support for quiet for TCL112 * Add & update unit tests. * Update supported models * General code style cleanups. For #1528 --- src/IRac.cpp | 12 +-- src/IRac.h | 4 +- src/ir_Tcl.cpp | 176 ++++++++++++++++++++++++++----------------- src/ir_Tcl.h | 25 ++++-- test/IRac_test.cpp | 33 +++++++- test/ir_Tcl_test.cpp | 162 ++++++++++++++++++++++++++++----------- 6 files changed, 281 insertions(+), 131 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 4988b67d7..c8f2b5270 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1854,6 +1854,7 @@ void IRac::sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, /// @param[in] fan The speed setting for the fan. /// @param[in] swingv The vertical swing setting. /// @param[in] swingh The horizontal swing setting. +/// @param[in] quiet Run the device in quiet/silent mode. /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] light Turn on the LED/Display mode. /// @param[in] econo Run the device in economical mode. @@ -1862,8 +1863,8 @@ void IRac::tcl112(IRTcl112Ac *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool light, const bool econo, - const bool filter) { + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter) { ac->begin(); ac->setPower(on); ac->setMode(ac->convertMode(mode)); @@ -1871,7 +1872,7 @@ void IRac::tcl112(IRTcl112Ac *ac, ac->setFan(ac->convertFan(fan)); ac->setSwingVertical(swingv != stdAc::swingv_t::kOff); ac->setSwingHorizontal(swingh != stdAc::swingh_t::kOff); - // No Quiet setting available. + ac->setQuiet(quiet); ac->setTurbo(turbo); ac->setLight(light); ac->setEcono(econo); @@ -2760,7 +2761,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { { IRTcl112Ac ac(_pin, _inverted, _modulation); tcl112(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, - send.swingh, send.turbo, send.light, send.econo, send.filter); + send.swingh, send.quiet, send.turbo, send.light, send.econo, + send.filter); break; } #endif // SEND_TCL112AC @@ -3958,7 +3960,7 @@ namespace IRAcUtils { case decode_type_t::TCL112AC: { IRTcl112Ac ac(kGpioUnused); ac.setRaw(decode->state); - *result = ac.toCommon(); + *result = ac.toCommon(prev); break; } #endif // DECODE_TCL112AC diff --git a/src/IRac.h b/src/IRac.h index 8f765ccbe..2b6321b72 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -403,8 +403,8 @@ void electra(IRElectraAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool light, const bool econo, - const bool filter); + const bool quiet, const bool turbo, const bool light, + const bool econo, const bool filter); #endif // SEND_TCL112AC #if SEND_TECHNIBEL_AC void technibel(IRTechnibelAc *ac, diff --git a/src/ir_Tcl.cpp b/src/ir_Tcl.cpp index 6ec95220b..669ceb4b6 100644 --- a/src/ir_Tcl.cpp +++ b/src/ir_Tcl.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 David Conran +// Copyright 2019, 2021 David Conran /// @file /// @brief Support for TCL protocols. @@ -53,6 +53,29 @@ void IRTcl112Ac::begin(void) { _irsend.begin(); } /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRTcl112Ac::send(const uint16_t repeat) { + uint8_t save[kTcl112AcStateLength]; + // Do we need to send the special "quiet" message? + if (_quiet != _quiet_prev) { + // Backup the current state. + std::memcpy(save, _.raw, kTcl112AcStateLength); + bool light_save = getLight(); + const uint8_t quiet_off[kTcl112AcStateLength] = { + 0x23, 0xCB, 0x26, 0x02, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65}; + // Use a known good quiet/mute off/type 2 state for the time being. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1528#issuecomment-876989044 + setRaw(quiet_off); + // Copy in the light and quiet states. + setLight(light_save); + setQuiet(_quiet); + // Send it. + _irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat); + // Now it's been sent, update the quiet previous state. + _quiet_prev = _quiet; + // Restore the old state. + setRaw(save); + } + // Send the normal (type 1) state. _irsend.sendTcl112Ac(getRaw(), kTcl112AcStateLength, repeat); } #endif // SEND_TCL112AC @@ -62,10 +85,15 @@ void IRTcl112Ac::send(const uint16_t repeat) { /// @param[in] length The length/size of the array. /// @return The calculated checksum value. uint8_t IRTcl112Ac::calcChecksum(uint8_t state[], const uint16_t length) { - if (length) - return sumBytes(state, length - 1); - else + if (length) { + if (length > 4 && state[3] == 0x02) { // Special nessage? + return sumBytes(state, length - 1, 0xF); // Checksum needs an offset. + } else { + return sumBytes(state, length - 1); + } + } else { return 0; + } } /// Calculate & set the checksum for the current internal state of the remote. @@ -91,6 +119,9 @@ void IRTcl112Ac::stateReset(void) { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03}; std::memcpy(_.raw, reset, kTcl112AcStateLength); + _quiet = false; + _quiet_prev = false; + _quiet_explictly_set = false; } /// Get a PTR to the internal state/code for this protocol. @@ -115,21 +146,15 @@ void IRTcl112Ac::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRTcl112Ac::setPower(const bool on) { - _.Power = on; -} +void IRTcl112Ac::setPower(const bool on) { _.Power = on; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getPower(void) const { - return _.Power; -} +bool IRTcl112Ac::getPower(void) const { return _.Power; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. -uint8_t IRTcl112Ac::getMode(void) const { - return _.Mode; -} +uint8_t IRTcl112Ac::getMode(void) const { return _.Mode; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. @@ -193,57 +218,39 @@ void IRTcl112Ac::setFan(const uint8_t speed) { /// Get the current fan speed setting. /// @return The current fan speed/mode. -uint8_t IRTcl112Ac::getFan(void) const { - return _.Fan; -} +uint8_t IRTcl112Ac::getFan(void) const { return _.Fan; } /// Set the economy setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRTcl112Ac::setEcono(const bool on) { - _.Econo = on; -} +void IRTcl112Ac::setEcono(const bool on) { _.Econo = on; } /// Get the economy setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getEcono(void) const { - return _.Econo; -} +bool IRTcl112Ac::getEcono(void) const { return _.Econo; } /// Set the Health (Filter) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRTcl112Ac::setHealth(const bool on) { - _.Health = on; -} +void IRTcl112Ac::setHealth(const bool on) { _.Health = on; } /// Get the Health (Filter) setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getHealth(void) const { - return _.Health; -} +bool IRTcl112Ac::getHealth(void) const { return _.Health; } /// Set the Light (LED/Display) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRTcl112Ac::setLight(const bool on) { - _.Light = !on; // Cleared when on. -} +void IRTcl112Ac::setLight(const bool on) { _.Light = !on; } // Cleared when on. /// Get the Light (LED/Display) setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getLight(void) const { - return !_.Light; -} +bool IRTcl112Ac::getLight(void) const { return !_.Light; } /// Set the horizontal swing setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRTcl112Ac::setSwingHorizontal(const bool on) { - _.SwingH = on; -} +void IRTcl112Ac::setSwingHorizontal(const bool on) { _.SwingH = on; } /// Get the horizontal swing setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getSwingHorizontal(void) const { - return _.SwingH; -} +bool IRTcl112Ac::getSwingHorizontal(void) const { return _.SwingH; } /// Set the vertical swing setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. @@ -253,9 +260,7 @@ void IRTcl112Ac::setSwingVertical(const bool on) { /// Get the vertical swing setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getSwingVertical(void) const { - return _.SwingV; -} +bool IRTcl112Ac::getSwingVertical(void) const { return _.SwingV; } /// Set the Turbo setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. @@ -269,8 +274,24 @@ void IRTcl112Ac::setTurbo(const bool on) { /// Get the Turbo setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRTcl112Ac::getTurbo(void) const { - return _.Turbo; +bool IRTcl112Ac::getTurbo(void) const { return _.Turbo; } + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRTcl112Ac::setQuiet(const bool on) { + _quiet_explictly_set = true; + _quiet = on; + if (_.MsgType == kTcl112AcSpecial) _.Quiet = on; +} + +/// Get the Quiet setting of the A/C. +/// @param[in] def The default value to use if we are not sure. +/// @return true, the setting is on. false, the setting is off. +bool IRTcl112Ac::getQuiet(const bool def) const { + if (_.MsgType == kTcl112AcSpecial) + return _.Quiet; + else + return _quiet_explictly_set ? _quiet : def; } /// Convert a stdAc::opmode_t enum into its native mode. @@ -326,26 +347,30 @@ stdAc::fanspeed_t IRTcl112Ac::toCommonFanSpeed(const uint8_t spd) { } /// Convert the current internal state into its stdAc::state_t equivalent. +/// @param[in] prev Ptr to the previous state if required. /// @return The stdAc equivalent of the native settings. -stdAc::state_t IRTcl112Ac::toCommon(void) const { +stdAc::state_t IRTcl112Ac::toCommon(const stdAc::state_t *prev) const { stdAc::state_t result; + // Start with the previous state if given it. + if (prev != NULL) result = *prev; result.protocol = decode_type_t::TCL112AC; result.model = -1; // Not supported. - result.power = _.Power; - result.mode = toCommonMode(_.Mode); - result.celsius = true; - result.degrees = getTemp(); - result.fanspeed = toCommonFanSpeed(_.Fan); - result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : - stdAc::swingv_t::kOff; - result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : - stdAc::swingh_t::kOff; - result.turbo = _.Turbo; result.light = getLight(); - result.filter = _.Health; - result.econo = _.Econo; + result.quiet = getQuiet(result.quiet); + // The rest only get updated if it is a "normal" message. + if (_.MsgType == kTcl112AcNormal) { + result.power = _.Power; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(_.Fan); + result.swingv = _.SwingV ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + result.turbo = _.Turbo; + result.filter = _.Health; + result.econo = _.Econo; + } // Not supported. - result.quiet = false; result.clean = false; result.beep = false; result.sleep = -1; @@ -357,19 +382,28 @@ stdAc::state_t IRTcl112Ac::toCommon(void) const { /// @return A human readable string. String IRTcl112Ac::toString(void) const { String result = ""; - result.reserve(140); // Reserve some heap for the string to reduce fragging. - result += addBoolToString(_.Power, kPowerStr, false); - result += addModeToString(_.Mode, kTcl112AcAuto, kTcl112AcCool, - kTcl112AcHeat, kTcl112AcDry, kTcl112AcFan); - result += addTempFloatToString(getTemp()); - result += addFanToString(_.Fan, kTcl112AcFanHigh, kTcl112AcFanLow, - kTcl112AcFanAuto, kTcl112AcFanAuto, kTcl112AcFanMed); - result += addBoolToString(_.Econo, kEconoStr); - result += addBoolToString(_.Health, kHealthStr); + result.reserve(150); // Reserve some heap for the string to reduce fragging. + result += addIntToString(_.MsgType, D_STR_TYPE, false); + switch (_.MsgType) { + case kTcl112AcNormal: + result += addBoolToString(_.Power, kPowerStr); + result += addModeToString(_.Mode, kTcl112AcAuto, kTcl112AcCool, + kTcl112AcHeat, kTcl112AcDry, kTcl112AcFan); + result += addTempFloatToString(getTemp()); + result += addFanToString(_.Fan, kTcl112AcFanHigh, kTcl112AcFanLow, + kTcl112AcFanAuto, kTcl112AcFanAuto, + kTcl112AcFanMed); + result += addBoolToString(_.Econo, kEconoStr); + result += addBoolToString(_.Health, kHealthStr); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.SwingV, kSwingVStr); + break; + case kTcl112AcSpecial: + result += addBoolToString(_.Quiet, kQuietStr); + break; + } result += addBoolToString(getLight(), kLightStr); - result += addBoolToString(_.Turbo, kTurboStr); - result += addBoolToString(_.SwingH, kSwingHStr); - result += addBoolToString(_.SwingV, kSwingVStr); return result; } diff --git a/src/ir_Tcl.h b/src/ir_Tcl.h index fded2fed5..b9f059627 100644 --- a/src/ir_Tcl.h +++ b/src/ir_Tcl.h @@ -1,10 +1,11 @@ -// Copyright 2019 David Conran +// Copyright 2019, 2021 David Conran /// @file /// @brief Support for TCL protocols. // Supports: // Brand: Leberg, Model: LBS-TOR07 A/C +// Brand: TCL, Model: TAC-09CHSD/XA31I A/C #ifndef IR_TCL_H_ #define IR_TCL_H_ @@ -23,12 +24,18 @@ union Tcl112Protocol{ uint8_t raw[kTcl112AcStateLength]; ///< The State in IR code form. struct { - // Byte 0~4 - uint8_t pad0[5]; + // Byte 0~2 + uint8_t pad0[3]; + // Byte 3 + uint8_t MsgType :2; + uint8_t :6; + // Byte 4 + uint8_t :8; // Byte 5 uint8_t :2; uint8_t Power :1; - uint8_t :3; + uint8_t :2; + uint8_t Quiet :1; uint8_t Light :1; uint8_t Econo :1; // Byte 6 @@ -83,6 +90,9 @@ const float kTcl112AcTempMin = 16.0; const uint8_t kTcl112AcSwingVOn = 0b111; const uint8_t kTcl112AcSwingVOff = 0b000; +// MsgType +const uint8_t kTcl112AcNormal = 0b01; +const uint8_t kTcl112AcSpecial = 0b10; // Classes /// Class for handling detailed TCL A/C messages. @@ -129,11 +139,13 @@ class IRTcl112Ac { bool getSwingVertical(void) const; void setTurbo(const bool on); bool getTurbo(void) const; + void setQuiet(const bool on); + bool getQuiet(const bool def = false) const; static uint8_t convertMode(const stdAc::opmode_t mode); static uint8_t convertFan(const stdAc::fanspeed_t speed); static stdAc::opmode_t toCommonMode(const uint8_t mode); static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); - stdAc::state_t toCommon(void) const; + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; String toString(void) const; #ifndef UNIT_TEST @@ -145,6 +157,9 @@ class IRTcl112Ac { /// @endcond #endif // UNIT_TEST Tcl112Protocol _; + bool _quiet_prev; + bool _quiet; + bool _quiet_explictly_set; void checksum(const uint16_t length = kTcl112AcStateLength); }; diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 74e530d7c..4e93856c3 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1422,8 +1422,9 @@ TEST(TestIRac, Tcl112) { IRac irac(kGpioUnused); IRrecv capture(kGpioUnused); char expected[] = - "Power: On, Mode: 3 (Cool), Temp: 20C, Fan: 3 (Medium), Econo: On, " - "Health: On, Light: On, Turbo: Off, Swing(H): On, Swing(V): Off"; + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 20C, Fan: 3 (Medium), " + "Econo: On, Health: On, Turbo: Off, Swing(H): On, Swing(V): Off, " + "Light: On"; ac.begin(); irac.tcl112(&ac, @@ -1433,6 +1434,7 @@ TEST(TestIRac, Tcl112) { stdAc::fanspeed_t::kMedium, // Fan speed stdAc::swingv_t::kOff, // Vertical swing stdAc::swingh_t::kAuto, // Horizontal swing + false, // Quiet (aka. Mute) false, // Turbo true, // Light true, // Econo @@ -1445,6 +1447,33 @@ TEST(TestIRac, Tcl112) { ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); + // Test the quiet mode, which should generate two messages. + ac._irsend.reset(); + irac.tcl112(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 20, // Celsius + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kOff, // Vertical swing + stdAc::swingh_t::kAuto, // Horizontal swing + true, // Quiet (aka. Mute) + false, // Turbo + true, // Light + true, // Econo + true); // Filter (aka. Health) + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(TCL112AC, ac._irsend.capture.decode_type); + ASSERT_EQ(kTcl112AcBits, ac._irsend.capture.bits); + ASSERT_EQ( + "Type: 2, Quiet: On, Light: On", + IRAcUtils::resultAcToString(&ac._irsend.capture)); + // TCL112 uses the Mitsubishi112 decoder. + // Skip first message. + EXPECT_TRUE(capture.decodeMitsubishi112(&ac._irsend.capture, 229)); + ASSERT_EQ(TCL112AC, ac._irsend.capture.decode_type); + ASSERT_EQ(kTcl112AcBits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); } TEST(TestIRac, Technibel) { diff --git a/test/ir_Tcl_test.cpp b/test/ir_Tcl_test.cpp index 99a9e7de7..01ce7f50a 100644 --- a/test/ir_Tcl_test.cpp +++ b/test/ir_Tcl_test.cpp @@ -1,6 +1,7 @@ // Copyright 2019 David Conran #include "ir_Tcl.h" +#include "IRac.h" #include "IRrecv.h" #include "IRrecv_test.h" #include "IRsend.h" @@ -17,8 +18,8 @@ TEST(TestTcl112Ac, Housekeeping) { // Decode a real Tcl112Ac A/C example from Issue #619 TEST(TestDecodeTcl112Ac, DecodeRealExample) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -61,19 +62,17 @@ TEST(TestDecodeTcl112Ac, DecodeRealExample) { ASSERT_EQ(TCL112AC, irsend.capture.decode_type); EXPECT_EQ(kTcl112AcBits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); - - IRTcl112Ac ac(0); - ac.setRaw(irsend.capture.state); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 24C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", - ac.toString()); + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 24C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", + IRAcUtils::resultAcToString(&irsend.capture)); } // Decode a synthetic Tcl112Ac A/C example from Issue #619 TEST(TestDecodeTcl112Ac, DecodeSyntheticExample) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -104,26 +103,30 @@ TEST(TestTcl112AcClass, Temperature) { const uint8_t temp31C[kTcl112AcStateLength] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBC}; - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.setRaw(temp16C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); ac.setRaw(temp16point5C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 16.5C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 16.5C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); ac.setRaw(temp19point5C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 19.5C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 19.5C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); ac.setRaw(temp31C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 31C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 31C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); ac.setTemp(kTcl112AcTempMin); @@ -161,7 +164,7 @@ TEST(TestTcl112AcClass, Temperature) { } TEST(TestTcl112AcClass, OperatingMode) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setMode(kTcl112AcAuto); @@ -203,13 +206,14 @@ TEST(TestTcl112AcClass, OperatingMode) { 0x07, 0x00, 0x00, 0x00, 0x00, 0x80, 0x48}; ac.setRaw(automode); EXPECT_EQ( - "Power: On, Mode: 8 (Auto), Temp: 24C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 8 (Auto), Temp: 24C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); } TEST(TestTcl112AcClass, Power) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setPower(true); @@ -232,8 +236,9 @@ TEST(TestTcl112AcClass, Power) { 0x0F, 0x00, 0x00, 0x00, 0x00, 0x80, 0xCB}; ac.setRaw(on); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); const uint8_t off[kTcl112AcStateLength] = { @@ -241,8 +246,9 @@ TEST(TestTcl112AcClass, Power) { 0x07, 0x40, 0x00, 0x00, 0x00, 0x80, 0xCB}; ac.setRaw(off); EXPECT_EQ( - "Power: Off, Mode: 3 (Cool), Temp: 24C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: Off, Mode: 3 (Cool), Temp: 24C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); } @@ -253,17 +259,19 @@ TEST(TestTcl112AcClass, Checksum) { uint8_t temp31C[kTcl112AcStateLength] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBC}; - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); EXPECT_EQ(0xCB, ac.calcChecksum(temp16C)); ac.setRaw(temp16C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 16C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); ac.setRaw(temp31C); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 31C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 31C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); EXPECT_EQ(0xBC, ac.calcChecksum(temp31C)); @@ -278,7 +286,7 @@ TEST(TestTcl112AcClass, Checksum) { } TEST(TestTcl112AcClass, Econo) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setEcono(true); @@ -290,7 +298,7 @@ TEST(TestTcl112AcClass, Econo) { } TEST(TestTcl112AcClass, Health) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setHealth(true); @@ -302,7 +310,7 @@ TEST(TestTcl112AcClass, Health) { } TEST(TestTcl112AcClass, Light) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setLight(true); @@ -314,7 +322,7 @@ TEST(TestTcl112AcClass, Light) { } TEST(TestTcl112AcClass, SwingHorizontal) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setSwingHorizontal(true); @@ -326,7 +334,7 @@ TEST(TestTcl112AcClass, SwingHorizontal) { } TEST(TestTcl112AcClass, SwingVertical) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setSwingVertical(true); @@ -338,7 +346,7 @@ TEST(TestTcl112AcClass, SwingVertical) { } TEST(TestTcl112AcClass, Turbo) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); ac.setFan(kTcl112AcFanLow); @@ -361,7 +369,7 @@ TEST(TestTcl112AcClass, Turbo) { } TEST(TestTcl112AcClass, FanSpeed) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.begin(); // Unexpected value should default to Auto. @@ -382,9 +390,21 @@ TEST(TestTcl112AcClass, FanSpeed) { EXPECT_EQ(kTcl112AcFanAuto, ac.getFan()); } +TEST(TestTcl112AcClass, Quiet_Mute) { + IRTcl112Ac ac(kGpioUnused); + ac.begin(); + + EXPECT_FALSE(ac.getQuiet()); + EXPECT_FALSE(ac.getQuiet(false)); + EXPECT_TRUE(ac.getQuiet(true)); + ac.setQuiet(true); + EXPECT_TRUE(ac.getQuiet()); + ac.setQuiet(false); + EXPECT_FALSE(ac.getQuiet()); +} TEST(TestTcl112AcClass, toCommon) { - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.setPower(true); ac.setMode(kTcl112AcCool); ac.setTemp(20); @@ -395,6 +415,7 @@ TEST(TestTcl112AcClass, toCommon) { ac.setHealth(true); ac.setEcono(true); ac.setLight(true); + ac.setQuiet(false); // Now test it. ASSERT_EQ(decode_type_t::TCL112AC, ac.toCommon().protocol); ASSERT_EQ(-1, ac.toCommon().model); @@ -409,17 +430,25 @@ TEST(TestTcl112AcClass, toCommon) { ASSERT_TRUE(ac.toCommon().econo); ASSERT_TRUE(ac.toCommon().light); ASSERT_TRUE(ac.toCommon().filter); + ASSERT_FALSE(ac.toCommon().quiet); // Unsupported. ASSERT_FALSE(ac.toCommon().clean); ASSERT_FALSE(ac.toCommon().beep); - ASSERT_FALSE(ac.toCommon().quiet); ASSERT_EQ(-1, ac.toCommon().sleep); ASSERT_EQ(-1, ac.toCommon().clock); + + // Tests for the quiet setting. + stdAc::state_t prev = ac.toCommon(); + prev.quiet = true; + ASSERT_FALSE(ac.toCommon(&prev).quiet); + ac.stateReset(); + // If the current quiet setting hasn't been explicitly set, use the previous. + ASSERT_TRUE(ac.toCommon(&prev).quiet); } TEST(TestDecodeTcl112Ac, Issue744) { - IRsendTest irsend(0); - IRrecv irrecv(0); + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); irsend.begin(); irsend.reset(); @@ -453,10 +482,51 @@ TEST(TestDecodeTcl112Ac, Issue744) { EXPECT_EQ(kTcl112AcBits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); - IRTcl112Ac ac(0); + IRTcl112Ac ac(kGpioUnused); ac.setRaw(irsend.capture.state); EXPECT_EQ( - "Power: On, Mode: 3 (Cool), Temp: 23C, Fan: 0 (Auto), Econo: Off, " - "Health: Off, Light: On, Turbo: Off, Swing(H): Off, Swing(V): Off", + "Type: 1, Power: On, Mode: 3 (Cool), Temp: 23C, Fan: 0 (Auto), " + "Econo: Off, Health: Off, Turbo: Off, Swing(H): Off, Swing(V): Off, " + "Light: On", ac.toString()); } + +TEST(TestDecodeTcl112Ac, Issue1528) { + IRsendTest irsend(kGpioUnused); + IRrecv irrecv(kGpioUnused); + irsend.begin(); + + irsend.reset(); + const uint16_t rawData[227] = { + 3040, 1632, 500, 1084, 502, 1084, 500, 318, 474, 344, 474, 344, 472, 1110, + 474, 344, 474, 344, 472, 1110, 474, 1110, 474, 344, 472, 1112, 474, 344, + 472, 346, 470, 1112, 472, 1112, 472, 346, 470, 1114, 470, 1114, 470, 348, + 468, 348, 468, 1116, 470, 348, 468, 350, 464, 354, 424, 1158, 426, 392, + 424, 394, 424, 394, 424, 392, 424, 392, 424, 394, 424, 392, 424, 392, 424, + 394, 424, 392, 424, 392, 424, 394, 424, 392, 460, 358, 460, 358, 458, 358, + 458, 358, 460, 358, 460, 358, 460, 1124, 460, 1124, 460, 358, 460, 358, + 458, 358, 458, 360, 482, 334, 484, 334, 484, 334, 484, 334, 484, 334, 484, + 334, 484, 334, 486, 332, 484, 334, 484, 332, 484, 334, 484, 332, 484, 334, + 484, 332, 484, 332, 484, 332, 486, 332, 484, 332, 484, 334, 484, 334, 484, + 332, 484, 334, 484, 334, 484, 332, 484, 332, 486, 332, 484, 334, 484, 334, + 484, 334, 484, 334, 484, 334, 484, 334, 484, 334, 484, 334, 482, 334, 482, + 334, 482, 336, 482, 336, 482, 336, 482, 336, 482, 336, 482, 336, 482, 336, + 482, 336, 480, 336, 480, 338, 480, 338, 480, 338, 480, 336, 480, 338, 480, + 338, 480, 338, 480, 338, 478, 1104, 478, 340, 478, 1104, 480, 338, 478, + 340, 478, 340, 476, 340, 476, 1106, 478}; // UNKNOWN 2757C6B2 + + uint8_t expectedState[kTcl112AcStateLength] = { + 0x23, 0xCB, 0x26, 0x02, 0x00, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85}; + + irsend.sendRaw(rawData, 227, 38000); + irsend.makeDecodeResult(); + + ASSERT_TRUE(irrecv.decode(&irsend.capture)); + ASSERT_EQ(TCL112AC, irsend.capture.decode_type); + EXPECT_EQ(kTcl112AcBits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "Type: 2, Quiet: On, Light: Off", + IRAcUtils::resultAcToString(&irsend.capture)); +}