From 187c533aad3f3f626f6d2beae9cdf6f77126ec5e Mon Sep 17 00:00:00 2001 From: David Conran Date: Tue, 30 Nov 2021 05:03:57 +1000 Subject: [PATCH] TOSHIBA_AC: Add Filter setting support. aka. Pure. (#1693) * Add `setFilter()` & `getFilter()`. * Add filter support for Toshiba in `IRAc` class. * Minor code style/formating cleanup. * Update & add unit tests for code coverage and verification. Fixes #1692 --- src/IRac.cpp | 7 +++--- src/IRac.h | 2 +- src/ir_Toshiba.cpp | 22 +++++++++++++---- src/ir_Toshiba.h | 38 +++++++++++++++++------------ test/IRac_test.cpp | 16 ++++++++----- test/ir_Toshiba_test.cpp | 52 +++++++++++++++++++++++++++++++++------- 6 files changed, 98 insertions(+), 39 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 43cc4ef55..964ca7285 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -2158,11 +2158,12 @@ void IRac::teco(IRTecoAc *ac, /// @param[in] swingv The vertical swing setting. /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (Pure/ion/pollen/etc) filter mode. void IRac::toshiba(IRToshibaAC *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, - const bool turbo, const bool econo) { + const bool turbo, const bool econo, const bool filter) { ac->begin(); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); @@ -2175,7 +2176,7 @@ void IRac::toshiba(IRToshibaAC *ac, ac->setTurbo(turbo); ac->setEcono(econo); // No Light setting available. - // No Filter setting available. + ac->setFilter(filter); // No Clean setting available. // No Beep setting available. // No Sleep setting available. @@ -3114,7 +3115,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { { IRToshibaAC ac(_pin, _inverted, _modulation); toshiba(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, - send.turbo, send.econo); + send.turbo, send.econo, send.filter); break; } #endif // SEND_TOSHIBA_AC diff --git a/src/IRac.h b/src/IRac.h index 2ac49baed..9954eecb0 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -466,7 +466,7 @@ void electra(IRElectraAc *ac, void toshiba(IRToshibaAC *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, - const bool turbo, const bool econo); + const bool turbo, const bool econo, const bool filter); #endif // SEND_TOSHIBA_AC #if SEND_TROTEC void trotec(IRTrotecESP *ac, diff --git a/src/ir_Toshiba.cpp b/src/ir_Toshiba.cpp index 0e3ac8ba2..7333493ef 100644 --- a/src/ir_Toshiba.cpp +++ b/src/ir_Toshiba.cpp @@ -216,9 +216,7 @@ void IRToshibaAC::setTemp(const uint8_t degrees) { /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. -uint8_t IRToshibaAC::getTemp(void) const { - return _.Temp + kToshibaAcMinTemp; -} +uint8_t IRToshibaAC::getTemp(void) const { return _.Temp + kToshibaAcMinTemp; } /// Set the speed of the fan. /// @param[in] speed The desired setting (0 is Auto, 1-5 is the speed, 5 is Max) @@ -339,6 +337,19 @@ void IRToshibaAC::setEcono(const bool on) { } } +/// Get the filter (Pure/Ion Filter) setting of the A/C. +/// @return true, if the current setting is on. Otherwise, false. +bool IRToshibaAC::getFilter(void) const { + return (getStateLength() >= kToshibaACStateLength) ? _.Filter : false; +} + +/// Set the filter (Pure/Ion Filter) setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRToshibaAC::setFilter(const bool on) { + _.Filter = on; + if (on) setStateLength(std::min(kToshibaACStateLength, getStateLength())); +} + /// Convert a stdAc::opmode_t enum into its native mode. /// @param[in] mode The enum to be converted. /// @return The native equivalent of the enum. @@ -421,6 +432,7 @@ stdAc::state_t IRToshibaAC::toCommon(const stdAc::state_t *prev) const { result.fanspeed = toCommonFanSpeed(getFan()); result.turbo = getTurbo(); result.econo = getEcono(); + result.filter = getFilter(); } switch (getSwing()) { case kToshibaAcSwingOn: @@ -436,7 +448,6 @@ stdAc::state_t IRToshibaAC::toCommon(const stdAc::state_t *prev) const { } // Not supported. result.light = false; - result.filter = false; result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.clean = false; @@ -450,7 +461,7 @@ stdAc::state_t IRToshibaAC::toCommon(const stdAc::state_t *prev) const { /// @return A human readable string. String IRToshibaAC::toString(void) const { String result = ""; - result.reserve(80); + result.reserve(95); result += addTempToString(getTemp(), true, false); switch (getStateLength()) { case kToshibaACStateLengthShort: @@ -477,6 +488,7 @@ String IRToshibaAC::toString(void) const { kToshibaAcFanMed); result += addBoolToString(getTurbo(), kTurboStr); result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(getFilter(), kFilterStr); } return result; } diff --git a/src/ir_Toshiba.h b/src/ir_Toshiba.h index 3ebf5e693..1314cf54d 100644 --- a/src/ir_Toshiba.h +++ b/src/ir_Toshiba.h @@ -8,6 +8,7 @@ /// @see https://docs.google.com/spreadsheets/d/1yidE2fvaO9kpCHfKafIdH31q4uaskYR1OwwrkyOxbp0/edit?usp=drivesdk /// @see https://www.toshiba-carrier.co.jp/global/about/index.htm /// @see http://www.toshiba-carrier.co.th/AboutUs/Pages/CompanyProfile.aspx +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1692 // Supports: // Brand: Toshiba, Model: RAS-B13N3KV2 @@ -18,6 +19,8 @@ // Brand: Toshiba, Model: WC-L03SE // Brand: Toshiba, Model: WH-UB03NJ remote // Brand: Toshiba, Model: RAS-2558V A/C +// Brand: Toshiba, Model: WH-TA01JE remote +// Brand: Toshiba, Model: RAS-25SKVP2-ND A/C // Brand: Carrier, Model: 42NQV060M2 / 38NYV060M2 A/C // Brand: Carrier, Model: 42NQV050M2 / 38NYV050M2 A/C // Brand: Carrier, Model: 42NQV035M2 / 38NYV035M2 A/C @@ -50,28 +53,31 @@ union ToshibaProtocol{ ///< 1 (56 bit message) ///< 3 (72 bit message) ///< 4 (80 bit message) - uint8_t Length :8; + uint8_t Length :8; // Byte[3] - The bit-inverted value of the "length" byte. - uint8_t :8; + uint8_t :8; // Byte[4] - uint8_t :3; - uint8_t LongMsg :1; - uint8_t :1; - uint8_t ShortMsg:1; - uint8_t :2; + uint8_t :3; + uint8_t LongMsg :1; + uint8_t :1; + uint8_t ShortMsg :1; + uint8_t :2; // Byte[5] - uint8_t Swing :3; - uint8_t :1; - uint8_t Temp :4; + uint8_t Swing :3; + uint8_t :1; + uint8_t Temp :4; // Byte[6] - uint8_t Mode :3; - uint8_t :2; - uint8_t Fan :3; + uint8_t Mode :3; + uint8_t :2; + uint8_t Fan :3; // Byte[7] - uint8_t :8; + uint8_t :4; + uint8_t Filter :1; + uint8_t :3; + // Byte[8] // (Checksum for 72 bit messages, Eco/Turbo for long 80 bit messages) - uint8_t EcoTurbo :8; + uint8_t EcoTurbo :8; }; }; @@ -144,6 +150,8 @@ class IRToshibaAC { bool getTurbo(void) const; void setEcono(const bool on); bool getEcono(void) const; + void setFilter(const bool on); + bool getFilter(void) const; void setMode(const uint8_t mode); uint8_t getMode(const bool raw = false) const; void setRaw(const uint8_t newState[], diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 1c2c94b79..a4e65c88d 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1989,7 +1989,7 @@ TEST(TestIRac, Toshiba) { IRrecv capture(kGpioUnused); char expected[] = "Temp: 29C, Power: On, Mode: 2 (Dry), Fan: 2 (UNKNOWN), " - "Turbo: Off, Econo: On"; + "Turbo: Off, Econo: On, Filter: Off"; ac.begin(); irac.toshiba(&ac, @@ -1999,7 +1999,8 @@ TEST(TestIRac, Toshiba) { stdAc::fanspeed_t::kLow, // Fan speed stdAc::swingv_t::kOff, // Vertical Swing false, // Turbo - true); // Econo + true, // Econo + false); // Filter ASSERT_EQ(expected, ac.toString()); ASSERT_EQ(kToshibaACStateLengthLong, ac.getStateLength()); ac._irsend.makeDecodeResult(); @@ -2904,7 +2905,7 @@ TEST(TestIRac, Issue1250) { // Now send the state so we can actually decode/capture what we sent. char expected_on[] = "Temp: 19C, Power: On, Mode: 4 (Fan), Fan: 0 (Auto), " - "Turbo: Off, Econo: Off"; + "Turbo: Off, Econo: Off, Filter: Off"; ac._irsend.reset(); irac.toshiba(&ac, irac.next.power, // Power @@ -2913,7 +2914,8 @@ TEST(TestIRac, Issue1250) { irac.next.fanspeed, // Fan speed irac.next.swingv, // Vertical Swing irac.next.turbo, // Turbo - irac.next.econo); // Econo + irac.next.econo, // Econo + irac.next.filter); // Filter ASSERT_EQ(expected_on, ac.toString()); ASSERT_EQ(kToshibaACStateLength, ac.getStateLength()); ac._irsend.makeDecodeResult(); @@ -2929,7 +2931,8 @@ TEST(TestIRac, Issue1250) { irac.sendAc(); // Now send the state so we can actually decode/capture what we sent. char expected_off[] = - "Temp: 19C, Power: Off, Fan: 0 (Auto), Turbo: Off, Econo: Off"; + "Temp: 19C, Power: Off, Fan: 0 (Auto), Turbo: Off, Econo: Off, " + "Filter: Off"; ac._irsend.reset(); irac.toshiba(&ac, irac.next.power, // Power @@ -2938,7 +2941,8 @@ TEST(TestIRac, Issue1250) { irac.next.fanspeed, // Fan speed irac.next.swingv, // Vertical Swing irac.next.turbo, // Turbo - irac.next.econo); // Econo + irac.next.econo, // Econo + irac.next.filter); // Filter ASSERT_EQ(expected_off, ac.toString()); ASSERT_EQ(kToshibaACStateLength, ac.getStateLength()); ac._irsend.makeDecodeResult(); diff --git a/test/ir_Toshiba_test.cpp b/test/ir_Toshiba_test.cpp index 1785b3895..4a63780d5 100644 --- a/test/ir_Toshiba_test.cpp +++ b/test/ir_Toshiba_test.cpp @@ -312,20 +312,21 @@ TEST(TestToshibaACClass, HumanReadableOutput) { ac.setRaw(initial_state); EXPECT_EQ("Temp: 17C, Power: On, Mode: 0 (Auto), Fan: 0 (Auto), " - "Turbo: Off, Econo: Off", + "Turbo: Off, Econo: Off, Filter: Off", ac.toString()); ac.setRaw(modified_state); EXPECT_EQ("Temp: 17C, Power: On, Mode: 1 (Cool), Fan: 5 (High), " - "Turbo: Off, Econo: Off", + "Turbo: Off, Econo: Off, Filter: Off", ac.toString()); ac.setTemp(25); ac.setFan(3); ac.setMode(kToshibaAcDry); EXPECT_EQ("Temp: 25C, Power: On, Mode: 2 (Dry), Fan: 3 (Medium), " - "Turbo: Off, Econo: Off", + "Turbo: Off, Econo: Off, Filter: Off", ac.toString()); ac.off(); - EXPECT_EQ("Temp: 25C, Power: Off, Fan: 3 (Medium), Turbo: Off, Econo: Off", + EXPECT_EQ("Temp: 25C, Power: Off, Fan: 3 (Medium), Turbo: Off, Econo: Off, " + "Filter: Off", ac.toString()); } @@ -379,7 +380,7 @@ TEST(TestDecodeToshibaAC, SyntheticExample) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Temp: 17C, Power: On, Mode: 0 (Auto), Fan: 0 (Auto), Turbo: Off, " - "Econo: Off", + "Econo: Off, Filter: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -555,6 +556,7 @@ TEST(TestToshibaACClass, toCommon) { ac.setMode(kToshibaAcCool); ac.setTemp(20); ac.setFan(kToshibaAcFanMax); + ac.setFilter(true); // Now test it. ASSERT_EQ(decode_type_t::TOSHIBA_AC, ac.toCommon().protocol); ASSERT_EQ(-1, ac.toCommon().model); @@ -563,13 +565,13 @@ TEST(TestToshibaACClass, toCommon) { ASSERT_EQ(20, ac.toCommon().degrees); ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode); ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed); + ASSERT_TRUE(ac.toCommon().filter); // Unsupported. ASSERT_EQ(stdAc::swingv_t::kOff, ac.toCommon().swingv); ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh); ASSERT_FALSE(ac.toCommon().turbo); ASSERT_FALSE(ac.toCommon().econo); ASSERT_FALSE(ac.toCommon().light); - ASSERT_FALSE(ac.toCommon().filter); ASSERT_FALSE(ac.toCommon().clean); ASSERT_FALSE(ac.toCommon().beep); ASSERT_FALSE(ac.toCommon().quiet); @@ -626,7 +628,7 @@ TEST(TestDecodeToshibaAC, RealLongExample) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Temp: 22C, Power: On, Mode: 0 (Auto), Fan: 0 (Auto), Turbo: On, " - "Econo: Off", + "Econo: Off, Filter: Off", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -731,7 +733,7 @@ TEST(TestToshibaACClass, ConstructLongState) { ac.setEcono(true); EXPECT_EQ( "Temp: 29C, Power: On, Mode: 2 (Dry), Fan: 2 (UNKNOWN), " - "Turbo: Off, Econo: On", + "Turbo: Off, Econo: On, Filter: Off", ac.toString()); EXPECT_EQ(kToshibaACStateLengthLong, ac.getStateLength()); const uint8_t expectedState[kToshibaACStateLengthLong] = { @@ -781,7 +783,8 @@ TEST(TestDecodeToshibaAC, RealExample_WHUB03NJ) { EXPECT_EQ(kToshibaACBits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Temp: 20C, Power: Off, Fan: 0 (Auto), Turbo: Off, Econo: Off", + "Temp: 20C, Power: Off, Fan: 0 (Auto), Turbo: Off, Econo: Off, " + "Filter: Off", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -828,3 +831,34 @@ TEST(TestToshibaACClass, SwingCodes) { "Temp: 17C, Swing(V): 4 (Toggle)", ac.toString()); } + +// For https://github.com/crankyoldgit/IRremoteESP8266/issues/1692 +TEST(TestToshibaACClass, Filter) { + IRToshibaAC ac(kGpioUnused); + ac.begin(); + EXPECT_FALSE(ac.getFilter()); + + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); + + + ac.setFilter(false); + EXPECT_FALSE(ac.getFilter()); + + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); + + const uint8_t pure_off[kToshibaACStateLength] = { + 0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x40, 0x03, 0x00, 0x42}; + ac.setRaw(pure_off); + EXPECT_FALSE(ac.getFilter()); + + const uint8_t pure_on[kToshibaACStateLength] = { + 0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x40, 0x03, 0x10, 0x52}; + ac.setRaw(pure_on); + EXPECT_TRUE(ac.getFilter()); + + // Convert a known filter/pure on state to a known off filter/pure state. + ac.setFilter(false); + EXPECT_STATE_EQ(pure_off, ac.getRaw(), ac.getStateLength() * 8); +}