From f706f99fd9db7fa67f1958387a18e7b06b50c5c5 Mon Sep 17 00:00:00 2001 From: David Conran Date: Thu, 16 Jun 2022 23:07:03 +1000 Subject: [PATCH] GREE: Add model support for `YX1FSF`/Soleus Air Windown A/C (#1823) * Allow detecting/setting new model. * Allow setting Econo(Energy Saver) operation mode. * Update supported model info. * Add unit tests to cover new changes. Fixes #1821 --- src/IRac.cpp | 2 ++ src/IRsend.h | 4 +++- src/IRtext.cpp | 1 + src/IRtext.h | 1 + src/IRutils.cpp | 7 ++++--- src/ir_Gree.cpp | 28 ++++++++++++++++++++++------ src/ir_Gree.h | 16 ++++++++++------ src/locale/defaults.h | 3 +++ test/ir_Gree_test.cpp | 42 +++++++++++++++++++++++++++++++++++++++++- 9 files changed, 87 insertions(+), 17 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 3daa17211..c98d440dc 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -3472,6 +3472,8 @@ int16_t IRac::strToModel(const char *str, const int16_t def) { return gree_ac_remote_model_t::YAW1F; } else if (!STRCASECMP(str, kYbofbStr)) { return gree_ac_remote_model_t::YBOFB; + } else if (!STRCASECMP(str, kYx1fsfStr)) { + return gree_ac_remote_model_t::YX1FSF; // Haier models } else if (!STRCASECMP(str, kV9014557AStr)) { return haier_ac176_remote_model_t::V9014557_A; diff --git a/src/IRsend.h b/src/IRsend.h index 1e70e8f67..79f1d6688 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -134,7 +134,9 @@ enum fujitsu_ac_remote_model_t { /// Gree A/C model numbers enum gree_ac_remote_model_t { YAW1F = 1, // (1) Ultimate, EKOKAI, RusClimate (Default) - YBOFB, // (2) Green, YBOFB2, YAPOF3 + YBOFB, // (2) Green, YBOFB2, YAPOF3 + YX1FSF, // (3) Soleus Air window unit (Similar to YAW1F, but with an + // Operation mode of Energy Saver (Econo)) }; /// HAIER_AC176 A/C model numbers diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 92ca34493..e1b905666 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -236,6 +236,7 @@ IRTEXT_CONST_STRING(kBitsStr, D_STR_BITS); ///< "Bits" // Model Names IRTEXT_CONST_STRING(kYaw1fStr, D_STR_YAW1F); ///< "YAW1F" IRTEXT_CONST_STRING(kYbofbStr, D_STR_YBOFB); ///< "YBOFB" +IRTEXT_CONST_STRING(kYx1fsfStr, D_STR_YX1FSF); ///< "YX1FSF" IRTEXT_CONST_STRING(kV9014557AStr, D_STR_V9014557_A); ///< "V9014557-A" IRTEXT_CONST_STRING(kV9014557BStr, D_STR_V9014557_B); ///< "V9014557-B" IRTEXT_CONST_STRING(kRlt0541htaaStr, D_STR_RLT0541HTA_A); ///< "R-LT0541-HTA-A" diff --git a/src/IRtext.h b/src/IRtext.h index aab671314..c21233761 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -235,6 +235,7 @@ extern IRTEXT_CONST_PTR(kXFanStr); extern IRTEXT_CONST_PTR(kYaw1fStr); extern IRTEXT_CONST_PTR(kYbofbStr); extern IRTEXT_CONST_PTR(kYesStr); +extern IRTEXT_CONST_PTR(kYx1fsfStr); extern IRTEXT_CONST_PTR(kZoneFollowStr); extern IRTEXT_CONST_PTR(kAllProtocolNamesStr); diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 66ef959b5..bb5e48b4a 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -610,9 +610,10 @@ namespace irutils { break; case decode_type_t::GREE: switch (model) { - case gree_ac_remote_model_t::YAW1F: return kYaw1fStr; - case gree_ac_remote_model_t::YBOFB: return kYbofbStr; - default: return kUnknownStr; + case gree_ac_remote_model_t::YAW1F: return kYaw1fStr; + case gree_ac_remote_model_t::YBOFB: return kYbofbStr; + case gree_ac_remote_model_t::YX1FSF: return kYx1fsfStr; + default: return kUnknownStr; } break; case decode_type_t::HAIER_AC176: diff --git a/src/ir_Gree.cpp b/src/ir_Gree.cpp index 16789b99b..ead7178e3 100644 --- a/src/ir_Gree.cpp +++ b/src/ir_Gree.cpp @@ -162,6 +162,7 @@ void IRGreeAC::setRaw(const uint8_t new_code[]) { else _model = gree_ac_remote_model_t::YBOFB; } + if (_.Mode == kGreeEcono) _model = gree_ac_remote_model_t::YX1FSF; } /// Calculate and set the checksum values for the internal state. @@ -186,7 +187,8 @@ bool IRGreeAC::validChecksum(const uint8_t state[], const uint16_t length) { void IRGreeAC::setModel(const gree_ac_remote_model_t model) { switch (model) { case gree_ac_remote_model_t::YAW1F: - case gree_ac_remote_model_t::YBOFB: _model = model; break; + case gree_ac_remote_model_t::YBOFB: + case gree_ac_remote_model_t::YX1FSF: _model = model; break; default: _model = gree_ac_remote_model_t::YAW1F; } } @@ -291,6 +293,7 @@ void IRGreeAC::setMode(const uint8_t new_mode) { case kGreeDry: setFan(1); break; case kGreeCool: case kGreeFan: + case kGreeEcono: case kGreeHeat: break; // If we get an unexpected mode, default to AUTO. default: mode = kGreeAuto; @@ -352,11 +355,17 @@ bool IRGreeAC::getTurbo(void) const { return _.Turbo; } /// Set the Econo setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRGreeAC::setEcono(const bool on) { _.Econo = on; } +void IRGreeAC::setEcono(const bool on) { + _.Econo = on; + if (on && getModel() == gree_ac_remote_model_t::YX1FSF) + setMode(kGreeEcono); +} /// Get the Econo setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRGreeAC::getEcono(void) const { return _.Econo; } +bool IRGreeAC::getEcono(void) const { + return _.Econo || getMode() == kGreeEcono; +} /// Set the Vertical Swing mode of the A/C. /// @param[in] automatic Do we use the automatic setting? @@ -589,7 +598,7 @@ stdAc::state_t IRGreeAC::toCommon(void) { result.swingv = toCommonSwingV(_.SwingV); result.swingh = toCommonSwingH(_.SwingH); result.turbo = _.Turbo; - result.econo = _.Econo; + result.econo = getEcono(); result.light = _.Light; result.clean = _.Xfan; result.sleep = _.Sleep ? 0 : -1; @@ -608,8 +617,15 @@ String IRGreeAC::toString(void) { result.reserve(220); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::GREE, _model, false); result += addBoolToString(_.Power, kPowerStr); - result += addModeToString(_.Mode, kGreeAuto, kGreeCool, kGreeHeat, - kGreeDry, kGreeFan); + if (_model == gree_ac_remote_model_t::YX1FSF && _.Mode == kGreeEcono) { + result += addIntToString(_.Mode, kModeStr); + result += kSpaceLBraceStr; + result += kEconoStr; + result += ')'; + } else { + result += addModeToString(_.Mode, kGreeAuto, kGreeCool, kGreeHeat, + kGreeDry, kGreeFan); + } result += addTempToString(getTemp(), !_.UseFahrenheit); result += addFanToString(_.Fan, kGreeFanMax, kGreeFanMin, kGreeFanAuto, kGreeFanAuto, kGreeFanMed); diff --git a/src/ir_Gree.h b/src/ir_Gree.h index be5ac31ce..eb9d4a00c 100644 --- a/src/ir_Gree.h +++ b/src/ir_Gree.h @@ -1,9 +1,10 @@ -// Copyright 2016 David Conran +// Copyright 2016-2022 David Conran /// @file /// @brief Support for Gree A/C protocols. /// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/GreeHeatpumpIR.h /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1508 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1821 // Supports: // Brand: Ultimate, Model: Heat Pump @@ -15,6 +16,7 @@ // Brand: Gree, Model: YAA1FBF remote // Brand: Gree, Model: YB1F2F remote // Brand: Gree, Model: YAN1F1 remote +// Brand: Gree, Model: YX1F2F remote (YX1FSF) // Brand: Gree, Model: VIR09HP115V1AH A/C // Brand: Gree, Model: VIR12HP230V1AH A/C // Brand: Amana, Model: PBC093G00CC A/C @@ -23,6 +25,7 @@ // Brand: Cooper & Hunter, Model: CH-S09FTXG A/C // Brand: Vailland, Model: YACIFB remote // Brand: Vailland, Model: VAI5-035WNI A/C +// Brand: Soleus Air, Model: window A/C (YX1FSF) #ifndef IR_GREE_H_ #define IR_GREE_H_ @@ -86,11 +89,12 @@ union GreeProtocol{ // Constants -const uint8_t kGreeAuto = 0; -const uint8_t kGreeCool = 1; -const uint8_t kGreeDry = 2; -const uint8_t kGreeFan = 3; -const uint8_t kGreeHeat = 4; +const uint8_t kGreeAuto = 0; +const uint8_t kGreeCool = 1; +const uint8_t kGreeDry = 2; +const uint8_t kGreeFan = 3; +const uint8_t kGreeHeat = 4; +const uint8_t kGreeEcono = 5; const uint8_t kGreeFanAuto = 0; const uint8_t kGreeFanMin = 1; diff --git a/src/locale/defaults.h b/src/locale/defaults.h index f87f310e7..70f99e69f 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -566,6 +566,9 @@ D_STR_INDIRECT " " D_STR_MODE #ifndef D_STR_YBOFB #define D_STR_YBOFB "YBOFB" #endif // D_STR_YBOFB +#ifndef D_STR_YX1FSF +#define D_STR_YX1FSF "YX1FSF" +#endif // D_STR_YX1FSF #ifndef D_STR_V9014557_A #define D_STR_V9014557_A "V9014557-A" #endif // D_STR_V9014557_A diff --git a/test/ir_Gree_test.cpp b/test/ir_Gree_test.cpp index cb1832f61..e0160577e 100644 --- a/test/ir_Gree_test.cpp +++ b/test/ir_Gree_test.cpp @@ -326,6 +326,9 @@ TEST(TestGreeClass, OperatingMode) { ac.setMode(kGreeHeat); EXPECT_EQ(kGreeHeat, ac.getMode()); + ac.setMode(kGreeEcono); + EXPECT_EQ(kGreeEcono, ac.getMode()); + ASSERT_NE(kGreeFanMax, 1); ac.setFan(kGreeFanMax); ac.setMode(kGreeDry); // Dry should lock the fan to speed 1. @@ -337,7 +340,7 @@ TEST(TestGreeClass, OperatingMode) { ac.setMode(kGreeFan); EXPECT_EQ(kGreeFan, ac.getMode()); - ac.setMode(kGreeHeat + 1); + ac.setMode(kGreeEcono + 1); EXPECT_EQ(kGreeAuto, ac.getMode()); ac.setMode(255); @@ -798,3 +801,40 @@ TEST(TestGreeClass, DisplayTempSource) { ac.setRaw(state); EXPECT_EQ(2, ac.getDisplayTempSource()); } + +TEST(TestUtils, Housekeeping) { + ASSERT_EQ("GREE", typeToString(decode_type_t::GREE)); + ASSERT_EQ(decode_type_t::GREE, strToDecodeType("GREE")); + ASSERT_TRUE(hasACState(decode_type_t::GREE)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::GREE)); + ASSERT_EQ(kGreeBits, IRsend::defaultBits(decode_type_t::GREE)); + ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::GREE)); + ASSERT_EQ(gree_ac_remote_model_t::YAW1F, IRac::strToModel("YAW1F")); + ASSERT_EQ(irutils::modelToStr(decode_type_t::GREE, + gree_ac_remote_model_t::YAW1F), "YAW1F"); + ASSERT_EQ(gree_ac_remote_model_t::YBOFB, IRac::strToModel("YBOFB")); + ASSERT_EQ(irutils::modelToStr(decode_type_t::GREE, + gree_ac_remote_model_t::YBOFB), "YBOFB"); + ASSERT_EQ(gree_ac_remote_model_t::YX1FSF, IRac::strToModel("YX1FSF")); + ASSERT_EQ(irutils::modelToStr(decode_type_t::GREE, + gree_ac_remote_model_t::YX1FSF), "YX1FSF"); +} + +TEST(TestGreeClass, Issue1821EnergySaver) { + IRGreeAC ac(kGpioUnused); + ac.begin(); + + // https://github.com/crankyoldgit/IRremoteESP8266/issues/1821#issue-1271458457 + const uint8_t energy[8] = {0x1D, 0x09, 0x60, 0x58, 0x00, 0x20, 0x00, 0xA0}; + + ac.setRaw(energy); + EXPECT_EQ(kGreeEcono, ac.getMode()); + EXPECT_TRUE(ac.getEcono()); + EXPECT_EQ(gree_ac_remote_model_t::YX1FSF, ac.getModel()); + EXPECT_EQ( + "Model: 3 (YX1FSF), Power: On, Mode: 5 (Econo), Temp: 77F, Fan: 1 (Low), " + "Turbo: Off, Econo: Off, IFeel: Off, WiFi: Off, XFan: Off, Light: On, " + "Sleep: Off, Swing(V) Mode: Manual, Swing(V): 0 (Last), " + "Swing(H): 0 (Off), Timer: Off, Display Temp: 0 (Off)", + ac.toString()); +}