From f286893d02f71004fa069efac8fadc8e4528af51 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Thu, 2 Sep 2021 12:39:21 +1000 Subject: [PATCH 1/4] SharpAc: Allow position control of SwingV * Add the ability to set swingV positions via `setSwingV()` - e.g. Coanda setting. - Several other undocumented positions discovered via experimentation. - May not work on all models. * Update & add unit tests accordingly. Fixes #1590 --- src/IRac.cpp | 2 +- src/ir_Sharp.cpp | 95 +++++++++++++++++++++++++-- src/ir_Sharp.h | 18 +++++- test/IRac_test.cpp | 2 +- test/ir_Sharp_test.cpp | 144 ++++++++++++++++++++++++++++------------- 5 files changed, 206 insertions(+), 55 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 9b4e10cbd..7e29b9270 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1908,7 +1908,7 @@ void IRac::sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); ac->setFan(ac->convertFan(fan, model)); - ac->setSwingToggle(swingv != stdAc::swingv_t::kOff); + ac->setSwingV(ac->convertSwingV(swingv)); // Econo deliberately not used as it cycles through 3 modes uncontrollably. // ac->setEconoToggle(econo); ac->setIon(filter); diff --git a/src/ir_Sharp.cpp b/src/ir_Sharp.cpp index 8cbace41e..3983a85a0 100644 --- a/src/ir_Sharp.cpp +++ b/src/ir_Sharp.cpp @@ -45,6 +45,7 @@ using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addModelToString; +using irutils::addSwingVToString; using irutils::addTempToString; using irutils::minsToString; @@ -544,16 +545,61 @@ void IRSharpAc::setTurbo(const bool on) { _.Special = kSharpAcSpecialTurbo; } +/// Get the Vertical Swing setting of the A/C. +/// @return The position of the Vertical Swing setting. +uint8_t IRSharpAc::getSwingV(void) const { return _.Swing; } + +/// Set the Vertical Swing setting of the A/C. +/// @note Some positions may not work on all models. +/// @param[in] position The desired position/setting. +void IRSharpAc::setSwingV(const uint8_t position) { + switch (position) { + case kSharpAcSwingVCoanda: + // Only allowed in Heat mode. + if (getMode() != kSharpAcHeat) { + setSwingV(kSharpAcSwingVLow); // Use the next lowest setting. + return; + } + // FALLTHRU + case kSharpAcSwingVHigh: + case kSharpAcSwingVMid: + case kSharpAcSwingVLow: + case kSharpAcSwingVAuto: + // All expected non-Off positions set the special bits. + _.Special = kSharpAcSpecialSwing; + // FALLTHRU + case kSharpAcSwingVOff1: // Technically valid, but we don't use them. + case kSharpAcSwingVOff2: // Technically valid, but we don't use them. + case kSharpAcSwingVOff: + _.Swing = position; + } +} + +/// Convert a standard A/C vertical swing into its native setting. +/// @param[in] position A stdAc::swingv_t position to convert. +/// @return The equivalent native horizontal swing position. +uint8_t IRSharpAc::convertSwingV(const stdAc::swingv_t position) { + switch (position) { + case stdAc::swingv_t::kHighest: + case stdAc::swingv_t::kHigh: return kSharpAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kSharpAcSwingVMid; + case stdAc::swingv_t::kLow: return kSharpAcSwingVLow; + case stdAc::swingv_t::kLowest: return kSharpAcSwingVCoanda; + case stdAc::swingv_t::kAuto: return kSharpAcSwingVAuto; + default: return kSharpAcSwingVOff; + } +} + /// Get the (vertical) Swing Toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getSwingToggle(void) const { - return _.Swing == kSharpAcSwingToggle; + return getSwingV() == kSharpAcSwingVAuto; } /// Set the (vertical) Swing Toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRSharpAc::setSwingToggle(const bool on) { - _.Swing = (on ? kSharpAcSwingToggle : kSharpAcSwingNoToggle); + setSwingV(on ? kSharpAcSwingVAuto : kSharpAcSwingVOff); if (on) _.Special = kSharpAcSpecialSwing; } @@ -765,6 +811,27 @@ stdAc::fanspeed_t IRSharpAc::toCommonFanSpeed(const uint8_t speed) const { } } +/// Convert a native vertical swing postion to it's common equivalent. +/// @param[in] pos A native position to convert. +/// @param[in] mode What operating mode are we in? +/// @return The common vertical swing position. +stdAc::swingv_t IRSharpAc::toCommonSwingV(const uint8_t pos, + const stdAc::opmode_t mode) const { + switch (pos) { + case kSharpAcSwingVHigh: return stdAc::swingv_t::kHighest; + case kSharpAcSwingVMid: return stdAc::swingv_t::kMiddle; + case kSharpAcSwingVLow: return stdAc::swingv_t::kLow; + case kSharpAcSwingVCoanda: // Coanda has mode dependent positionss + switch (mode) { + case stdAc::opmode_t::kCool: return stdAc::swingv_t::kHighest; + case stdAc::opmode_t::kHeat: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kOff; + } + case kSharpAcSwingVAuto: return stdAc::swingv_t::kAuto; + default: return stdAc::swingv_t::kOff; + } +} + /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRSharpAc::toCommon(void) const { @@ -777,8 +844,7 @@ stdAc::state_t IRSharpAc::toCommon(void) const { result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.turbo = getTurbo(); - result.swingv = getSwingToggle() ? stdAc::swingv_t::kAuto - : stdAc::swingv_t::kOff; + result.swingv = toCommonSwingV(getSwingV(), result.mode); result.filter = _.Ion; result.econo = getEconoToggle(); result.light = getLightToggle(); @@ -797,14 +863,15 @@ stdAc::state_t IRSharpAc::toCommon(void) const { String IRSharpAc::toString(void) const { String result = ""; const sharp_ac_remote_model_t model = getModel(); - result.reserve(160); // Reserve some heap for the string to reduce fragging. + result.reserve(170); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::SHARP_AC, getModel(), false); result += addLabeledString(isPowerSpecial() ? "-" : (getPower() ? kOnStr : kOffStr), kPowerStr); + const uint8_t mode = _.Mode; result += addModeToString( - _.Mode, + mode, // Make the value invalid if the model doesn't support an Auto mode. (model == sharp_ac_remote_model_t::A907) ? kSharpAcAuto : 255, kSharpAcCool, kSharpAcHeat, kSharpAcDry, kSharpAcFan); @@ -821,8 +888,22 @@ String IRSharpAc::toString(void) const { kSharpAcFanAuto, kSharpAcFanAuto, kSharpAcFanMed); } + result += addSwingVToString( + getSwingV(), kSharpAcSwingVAuto, + // Coanda means Highest when in Cool mode. + (mode == kSharpAcCool) ? kSharpAcSwingVCoanda : kSharpAcSwingVAuto, + kSharpAcSwingVHigh, + kSharpAcSwingVAuto, // Upper Middle is unused + kSharpAcSwingVMid, + kSharpAcSwingVAuto, // Lower Middle is unused + kSharpAcSwingVLow, + kSharpAcSwingVCoanda, + kSharpAcSwingVOff, + // Below are unused. + kSharpAcSwingVAuto, + kSharpAcSwingVAuto, + kSharpAcSwingVAuto); result += addBoolToString(getTurbo(), kTurboStr); - result += addBoolToString(getSwingToggle(), kSwingVToggleStr); result += addBoolToString(_.Ion, kIonStr); switch (model) { case sharp_ac_remote_model_t::A705: diff --git a/src/ir_Sharp.h b/src/ir_Sharp.h index bf11877f6..e5f86c2a3 100644 --- a/src/ir_Sharp.h +++ b/src/ir_Sharp.h @@ -122,8 +122,16 @@ const uint8_t kSharpAcTimerHoursMax = 0b1100; // 12 const uint8_t kSharpAcOffTimerType = 0b0; const uint8_t kSharpAcOnTimerType = 0b1; -const uint8_t kSharpAcSwingToggle = 0b111; -const uint8_t kSharpAcSwingNoToggle = 0b000; +// Ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1260213 +const uint8_t kSharpAcSwingVOff = 0b000; // Stop swinging. +const uint8_t kSharpAcSwingVHigh = 0b001; // 0° down aka Coanda (Cool) +const uint8_t kSharpAcSwingVOff1 = 0b010; // Off, but go to last fixed pos. +const uint8_t kSharpAcSwingVMid = 0b011; // 30° down +const uint8_t kSharpAcSwingVLow = 0b100; // 45° down +const uint8_t kSharpAcSwingVOff2 = 0b101; // Off, but go to last fixed pos. +const uint8_t kSharpAcSwingVCoanda = 0b110; // 0° down (Cool), 75° down (Heat) +const uint8_t kSharpAcSwingVLowest = kSharpAcSwingVCoanda; +const uint8_t kSharpAcSwingVAuto = 0b111; // Constant swinging const uint8_t kSharpAcSpecialPower = 0x00; const uint8_t kSharpAcSpecialTurbo = 0x01; @@ -167,6 +175,8 @@ class IRSharpAc { void setTurbo(const bool on); bool getSwingToggle(void) const; void setSwingToggle(const bool on); + uint8_t getSwingV(void) const; + void setSwingV(const uint8_t position); bool getIon(void) const; void setIon(const bool on); bool getEconoToggle(void) const; @@ -188,8 +198,12 @@ class IRSharpAc { static uint8_t convertFan(const stdAc::fanspeed_t speed, const sharp_ac_remote_model_t model = sharp_ac_remote_model_t::A907); + static uint8_t convertSwingV(const stdAc::swingv_t position); stdAc::opmode_t toCommonMode(const uint8_t mode) const; stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed) const; + stdAc::swingv_t toCommonSwingV( + const uint8_t pos, + const stdAc::opmode_t mode = stdAc::opmode_t::kHeat) const; stdAc::state_t toCommon(void) const; String toString(void) const; #ifndef UNIT_TEST diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 140e50137..217486cac 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1610,7 +1610,7 @@ TEST(TestIRac, Sharp) { IRrecv capture(kGpioUnused); char expected[] = "Model: 1 (A907), Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 3 (Medium), " - "Turbo: Off, Swing(V) Toggle: On, Ion: On, Econo: -, Clean: Off"; + "Swing(V): 7 (Auto), Turbo: Off, Ion: On, Econo: -, Clean: Off"; ac.begin(); irac.sharp(&ac, diff --git a/test/ir_Sharp_test.cpp b/test/ir_Sharp_test.cpp index bf08c644a..d8e208c57 100644 --- a/test/ir_Sharp_test.cpp +++ b/test/ir_Sharp_test.cpp @@ -410,8 +410,8 @@ TEST(TestDecodeSharpAc, RealExample) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 27C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 2 (Cool), Temp: 27C, Fan: 2 (Auto), " + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -561,7 +561,7 @@ TEST(TestSharpAcClass, OperatingMode) { // Check toString() says Fan rather than Auto. EXPECT_EQ( "Model: 2 (A705), Power: Off, Mode: 0 (Fan), Temp: 15C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); } @@ -615,8 +615,8 @@ TEST(TestSharpAcClass, ReconstructKnownState) { EXPECT_STATE_EQ(on_auto_auto, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_auto_28[kSharpAcStateLength] = { @@ -629,8 +629,8 @@ TEST(TestSharpAcClass, ReconstructKnownState) { ac.setTemp(28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); EXPECT_STATE_EQ(cool_auto_28, ac.getRaw(), kSharpAcBits); } @@ -647,8 +647,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(off_auto_auto); EXPECT_EQ( "Model: 1 (A907), " - "Power: Off, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: Off, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), " + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t on_auto_auto[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0x00, 0x11, 0x20, 0x00, 0x08, 0x80, 0x00, 0xE0, @@ -657,8 +657,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(on_auto_auto); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_auto_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x22, 0x00, 0x08, 0x80, 0x04, 0xE0, @@ -667,8 +667,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_auto_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan1_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x42, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -677,8 +677,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan1_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 4 (Low), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 4 (Low), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan2_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x32, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -688,7 +688,7 @@ TEST(TestSharpAcClass, KnownStates) { EXPECT_EQ( "Model: 1 (A907), " "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 3 (Medium), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan3_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x52, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -698,7 +698,7 @@ TEST(TestSharpAcClass, KnownStates) { EXPECT_EQ( "Model: 1 (A907), " "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 5 (UNKNOWN), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan4_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x72, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -707,8 +707,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan4_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan4_28_ion_on[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x61, 0x72, 0x08, 0x08, 0x80, 0x00, 0xE4, @@ -717,8 +717,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan4_28_ion_on); EXPECT_EQ( "Model: 1 (A907), " - "Power: -, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: On, Econo: -, Clean: Off", + "Power: -, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (Off), " + "Turbo: Off, Ion: On, Econo: -, Clean: Off", ac.toString()); /* Unsupported / Not yet reverse engineered. uint8_t cool_fan4_28_eco1[kSharpAcStateLength] = { @@ -735,8 +735,8 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(dry_auto); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); } @@ -868,20 +868,20 @@ TEST(TestSharpAcClass, Turbo) { EXPECT_EQ(kSharpAcFanMax, ac.getFan()); EXPECT_EQ( "Model: 3 (A903), " - "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Turbo: On, " - "Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off", + "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " + "Swing(V): 0 (Off), Turbo: On, Ion: On, Light: -, Clean: Off", ac.toString()); ac.setRaw(off_state); EXPECT_FALSE(ac.getTurbo()); EXPECT_EQ( "Model: 3 (A903), " - "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off", + "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " + "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); } -TEST(TestSharpAcClass, SwingToggle) { +TEST(TestSharpAcClass, Swings) { IRSharpAc ac(kGpioUnused); ac.begin(); @@ -906,6 +906,62 @@ TEST(TestSharpAcClass, SwingToggle) { ac.setRaw(off_state); EXPECT_FALSE(ac.getSwingToggle()); + + // Vertical + ac.setSwingV(kSharpAcSwingVAuto); + EXPECT_EQ(kSharpAcSwingVAuto, ac.getSwingV()); + EXPECT_TRUE(ac.getSwingToggle()); + + ac.setSwingV(kSharpAcSwingVHigh); + EXPECT_EQ(kSharpAcSwingVHigh, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + + ac.setSwingV(0xFF); // Doesn't change if invalid position given. + EXPECT_EQ(kSharpAcSwingVHigh, ac.getSwingV()); + + ac.setSwingV(kSharpAcSwingVMid); + EXPECT_EQ(kSharpAcSwingVMid, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + + ac.setSwingV(kSharpAcSwingVLow); + EXPECT_EQ(kSharpAcSwingVLow, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + + ac.setSwingV(kSharpAcSwingVOff); + EXPECT_EQ(kSharpAcSwingVOff, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + + // Lowest/Coanda only works in Heat mode. + ac.setMode(kSharpAcCool); + ac.setSwingV(kSharpAcSwingVLowest); + EXPECT_EQ(kSharpAcSwingVLow, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + ac.setModel(sharp_ac_remote_model_t::A907); // Model A907 has heat mode. + ac.setMode(kSharpAcHeat); + EXPECT_EQ(kSharpAcHeat, ac.getMode()); + ac.setSwingV(kSharpAcSwingVLowest); + EXPECT_EQ(kSharpAcSwingVLowest, ac.getSwingV()); + + // Real messages/states + // ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1254748 + ac.stateReset(); + const uint8_t coanda_heat_on[13] = { + 0xAA, 0x5A, 0xCF, 0x10, 0xC8, 0x31, 0x21, + 0x0A, 0x0E, 0x80, 0x06, 0xF4, 0x81}; + ac.setRaw(coanda_heat_on); + EXPECT_EQ( + "Model: 3 (A903), Power: On, Mode: 1 (Heat), Temp: 23C, Fan: 2 (Auto), " + "Swing(V): 6 (Lowest), Turbo: Off, Ion: On, Light: -, Clean: Off", + ac.toString()); + + const uint8_t coanda_cool_on[13] = { + 0xAA, 0x5A, 0xCF, 0x10, 0xC7, 0x31, 0x22, + 0x0A, 0x0E, 0x80, 0x06, 0xF4, 0x41}; + ac.setRaw(coanda_cool_on); + EXPECT_EQ( + "Model: 3 (A903), Power: On, Mode: 2 (Cool), Temp: 22C, Fan: 2 (Auto), " + "Swing(V): 6 (Highest), Turbo: Off, Ion: On, Light: -, Clean: Off", + ac.toString()); } TEST(TestSharpAcClass, Ion) { @@ -1005,8 +1061,8 @@ TEST(TestSharpAcClass, Timers) { EXPECT_TRUE(ac.isPowerSpecial()); EXPECT_EQ( "Model: 3 (A903), " - "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Turbo: Off, " - "Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off, Off Timer: 08:30", + "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Swing(V): 0 (Off), " + "Turbo: Off, Ion: On, Light: -, Clean: Off, Off Timer: 08:30", ac.toString()); // ref: https://docs.google.com/spreadsheets/d/1otzVFM5_tegrZ4ROCLgQ_jvJaWCDlZs1vC-YuR1FFXM/edit#gid=0&range=E80 @@ -1020,7 +1076,7 @@ TEST(TestSharpAcClass, Timers) { EXPECT_TRUE(ac.isPowerSpecial()); EXPECT_EQ( "Model: 3 (A903), Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off, " + "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off, " "On Timer: 12:00", ac.toString()); } @@ -1058,13 +1114,13 @@ TEST(TestSharpAcClass, Clean) { EXPECT_TRUE(ac.getClean()); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Light: -, Clean: On", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: On", ac.toString()); ac.setRaw(clean_off_state); EXPECT_FALSE(ac.getClean()); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); // Try constructing the clean on state. @@ -1084,13 +1140,13 @@ TEST(TestSharpAcClass, Clean) { ac.setPower(false); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // Clean ON ac.setClean(true); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: On", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: On", ac.toString()); // Clean OFF (state is identical to `off_msg`). // i.e. It just clears the clean settings & turns off the device. @@ -1098,25 +1154,25 @@ TEST(TestSharpAcClass, Clean) { ac.setPower(false, true); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // Clean ON ac.setClean(true); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: On", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: On", ac.toString()); // AC OFF ac.off(); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // AC ON (Mode Cool, Temp 25, Ion OFF, Fan 7) ac.on(); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); } @@ -1126,7 +1182,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.stateReset(); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 0 (Auto), Temp: 15C, Fan: 0 (UNKNOWN), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); const uint8_t issue1309_on[13] = { @@ -1135,7 +1191,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.setRaw(issue1309_on); EXPECT_EQ( "Model: 2 (A705), Power: On, Mode: 2 (Cool), Temp: 16C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); EXPECT_STATE_EQ(issue1309_on, ac.getRaw(), kSharpAcBits); @@ -1147,7 +1203,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.on(); EXPECT_EQ( "Model: 2 (A705), Power: On, Mode: 2 (Cool), Temp: 16C, Fan: 2 (Auto), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); } @@ -1200,13 +1256,13 @@ TEST(TestSharpAcClass, Issue1387Power) { EXPECT_STATE_EQ(real_off, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 3 (A903), Power: Off, Mode: 2 (Cool), Temp: 27C, Fan: 3 (Low), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); // Create the same off state. ac.setPower(true, ac.getPower()); EXPECT_STATE_EQ(real_on, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 2 (Cool), Temp: 27C, Fan: 3 (Low), " - "Turbo: Off, Swing(V) Toggle: Off, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); } From e44905bbe66ad604604087a59b8cd282ea7467ed Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Fri, 3 Sep 2021 09:59:42 +1000 Subject: [PATCH 2/4] Enable forcing of Coanda mode in Cool. * Modify `setSwingV()` to take an optional parameter to override the heat mode check. Ref: https://github.com/crankyoldgit/IRremoteESP8266/pull/1594#issuecomment-912136379 --- src/ir_Sharp.cpp | 5 +++-- src/ir_Sharp.h | 2 +- test/ir_Sharp_test.cpp | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ir_Sharp.cpp b/src/ir_Sharp.cpp index 3983a85a0..3d7d2ef0b 100644 --- a/src/ir_Sharp.cpp +++ b/src/ir_Sharp.cpp @@ -552,11 +552,12 @@ uint8_t IRSharpAc::getSwingV(void) const { return _.Swing; } /// Set the Vertical Swing setting of the A/C. /// @note Some positions may not work on all models. /// @param[in] position The desired position/setting. -void IRSharpAc::setSwingV(const uint8_t position) { +/// @param[in] force Do we override the safety checks and just do it? +void IRSharpAc::setSwingV(const uint8_t position, const bool force) { switch (position) { case kSharpAcSwingVCoanda: // Only allowed in Heat mode. - if (getMode() != kSharpAcHeat) { + if (!force && getMode() != kSharpAcHeat) { setSwingV(kSharpAcSwingVLow); // Use the next lowest setting. return; } diff --git a/src/ir_Sharp.h b/src/ir_Sharp.h index e5f86c2a3..443ed62ac 100644 --- a/src/ir_Sharp.h +++ b/src/ir_Sharp.h @@ -176,7 +176,7 @@ class IRSharpAc { bool getSwingToggle(void) const; void setSwingToggle(const bool on); uint8_t getSwingV(void) const; - void setSwingV(const uint8_t position); + void setSwingV(const uint8_t position, const bool force = false); bool getIon(void) const; void setIon(const bool on); bool getEconoToggle(void) const; diff --git a/test/ir_Sharp_test.cpp b/test/ir_Sharp_test.cpp index d8e208c57..0c0f9ab69 100644 --- a/test/ir_Sharp_test.cpp +++ b/test/ir_Sharp_test.cpp @@ -942,6 +942,14 @@ TEST(TestSharpAcClass, Swings) { ac.setSwingV(kSharpAcSwingVLowest); EXPECT_EQ(kSharpAcSwingVLowest, ac.getSwingV()); + // Check we can force Coanda in Cool mode. + ac.setMode(kSharpAcCool); + ASSERT_EQ(kSharpAcSwingVCoanda, kSharpAcSwingVLowest); + ac.setSwingV(kSharpAcSwingVCoanda, true); + EXPECT_EQ(kSharpAcSwingVCoanda, ac.getSwingV()); + EXPECT_FALSE(ac.getSwingToggle()); + EXPECT_EQ(kSharpAcCool, ac.getMode()); + // Real messages/states // ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1254748 ac.stateReset(); From 98e789bf9b967b05e20d36c363f6aff03549e603 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 4 Sep 2021 12:06:18 +1000 Subject: [PATCH 3/4] Make changes to SwingV operation. * Change name of some settings to better match their action/function. * Use previous state to influence settings sent via `IRac` class. * Update unit tests. * Update `toString()` output. Ref: https://github.com/crankyoldgit/IRremoteESP8266/pull/1594#issuecomment-912464500 --- src/IRac.cpp | 16 +++++---- src/IRac.h | 3 +- src/ir_Sharp.cpp | 78 +++++++++++++++++++++++------------------- src/ir_Sharp.h | 10 +++--- test/IRac_test.cpp | 3 +- test/ir_Sharp_test.cpp | 71 +++++++++++++++++++------------------- 6 files changed, 96 insertions(+), 85 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 7e29b9270..87580e526 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1893,6 +1893,7 @@ void IRac::sanyo88(IRSanyoAc88 *ac, /// @param[in] degrees The temperature setting in degrees. /// @param[in] fan The speed setting for the fan. /// @param[in] swingv The vertical swing setting. +/// @param[in] swingv_prev The previous vertical swing setting. /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] light Turn on the LED/Display mode. /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. @@ -1901,14 +1902,15 @@ void IRac::sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, const bool on, const bool prev_power, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, - const stdAc::swingv_t swingv, const bool turbo, + const stdAc::swingv_t swingv, + const stdAc::swingv_t swingv_prev, const bool turbo, const bool light, const bool filter, const bool clean) { ac->begin(); ac->setModel(model); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); ac->setFan(ac->convertFan(fan, model)); - ac->setSwingV(ac->convertSwingV(swingv)); + if (swingv != swingv_prev) ac->setSwingV(ac->convertSwingV(swingv)); // Econo deliberately not used as it cycles through 3 modes uncontrollably. // ac->setEconoToggle(econo); ac->setIon(filter); @@ -2495,10 +2497,10 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { #if (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC) const bool prev_power = (prev != NULL) ? prev->power : !send.power; #endif // (SEND_HITACHI_AC1 || SEND_SAMSUNG_AC || SEND_SHARP_AC) -#if SEND_LG +#if (SEND_LG || SEND_SHARP_AC) const stdAc::swingv_t prev_swingv = (prev != NULL) ? prev->swingv : stdAc::swingv_t::kOff; -#endif // SEND_LG +#endif // (SEND_LG || SEND_SHARP_AC) // Per vendor settings & setup. switch (send.protocol) { #if SEND_AIRWELL @@ -2899,8 +2901,8 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { { IRSharpAc ac(_pin, _inverted, _modulation); sharp(&ac, (sharp_ac_remote_model_t)send.model, send.power, prev_power, - send.mode, degC, send.fanspeed, send.swingv, send.turbo, send.light, - send.filter, send.clean); + send.mode, degC, send.fanspeed, send.swingv, prev_swingv, + send.turbo, send.light, send.filter, send.clean); break; } #endif // SEND_SHARP_AC @@ -4136,7 +4138,7 @@ namespace IRAcUtils { case decode_type_t::SHARP_AC: { IRSharpAc ac(kGpioUnused); ac.setRaw(decode->state); - *result = ac.toCommon(); + *result = ac.toCommon(prev); break; } #endif // DECODE_SHARP_AC diff --git a/src/IRac.h b/src/IRac.h index 573e7ae7e..18488c3bd 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -413,7 +413,8 @@ void electra(IRElectraAc *ac, void sharp(IRSharpAc *ac, const sharp_ac_remote_model_t model, const bool on, const bool prev_power, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, - const stdAc::swingv_t swingv, const bool turbo, const bool light, + const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, + const bool turbo, const bool light, const bool filter, const bool clean); #endif // SEND_SHARP_AC #if SEND_TCL112AC diff --git a/src/ir_Sharp.cpp b/src/ir_Sharp.cpp index 3d7d2ef0b..190fe1667 100644 --- a/src/ir_Sharp.cpp +++ b/src/ir_Sharp.cpp @@ -565,13 +565,13 @@ void IRSharpAc::setSwingV(const uint8_t position, const bool force) { case kSharpAcSwingVHigh: case kSharpAcSwingVMid: case kSharpAcSwingVLow: - case kSharpAcSwingVAuto: - // All expected non-Off positions set the special bits. + case kSharpAcSwingVToggle: + case kSharpAcSwingVOff: + case kSharpAcSwingVLast: // Technically valid, but we don't use it. + // All expected non-positions set the special bits. _.Special = kSharpAcSpecialSwing; // FALLTHRU - case kSharpAcSwingVOff1: // Technically valid, but we don't use them. - case kSharpAcSwingVOff2: // Technically valid, but we don't use them. - case kSharpAcSwingVOff: + case kSharpAcSwingVIgnore: _.Swing = position; } } @@ -586,29 +586,28 @@ uint8_t IRSharpAc::convertSwingV(const stdAc::swingv_t position) { case stdAc::swingv_t::kMiddle: return kSharpAcSwingVMid; case stdAc::swingv_t::kLow: return kSharpAcSwingVLow; case stdAc::swingv_t::kLowest: return kSharpAcSwingVCoanda; - case stdAc::swingv_t::kAuto: return kSharpAcSwingVAuto; - default: return kSharpAcSwingVOff; + case stdAc::swingv_t::kAuto: return kSharpAcSwingVToggle; + case stdAc::swingv_t::kOff: return kSharpAcSwingVOff; + default: return kSharpAcSwingVIgnore; } } /// Get the (vertical) Swing Toggle setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRSharpAc::getSwingToggle(void) const { - return getSwingV() == kSharpAcSwingVAuto; + return getSwingV() == kSharpAcSwingVToggle; } /// Set the (vertical) Swing Toggle setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRSharpAc::setSwingToggle(const bool on) { - setSwingV(on ? kSharpAcSwingVAuto : kSharpAcSwingVOff); + setSwingV(on ? kSharpAcSwingVToggle : kSharpAcSwingVIgnore); if (on) _.Special = kSharpAcSpecialSwing; } /// Get the Ion (Filter) setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRSharpAc::getIon(void) const { - return _.Ion; -} +bool IRSharpAc::getIon(void) const { return _.Ion; } /// Set the Ion (Filter) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. @@ -675,15 +674,11 @@ uint16_t IRSharpAc::getTimerTime(void) const { /// Is the Timer enabled? /// @return true, the setting is on. false, the setting is off. -bool IRSharpAc::getTimerEnabled(void) const { - return _.TimerEnabled; -} +bool IRSharpAc::getTimerEnabled(void) const { return _.TimerEnabled; } /// Get the current timer type. /// @return true, It's an "On" timer. false, It's an "Off" timer. -bool IRSharpAc::getTimerType(void) const { - return _.TimerType; -} +bool IRSharpAc::getTimerType(void) const { return _.TimerType; } /// Set or cancel the timer function. /// @param[in] enable Is the timer to be enabled (true) or canceled(false)? @@ -828,15 +823,18 @@ stdAc::swingv_t IRSharpAc::toCommonSwingV(const uint8_t pos, case stdAc::opmode_t::kHeat: return stdAc::swingv_t::kLowest; default: return stdAc::swingv_t::kOff; } - case kSharpAcSwingVAuto: return stdAc::swingv_t::kAuto; + case kSharpAcSwingVToggle: return stdAc::swingv_t::kAuto; default: return stdAc::swingv_t::kOff; } } /// 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 IRSharpAc::toCommon(void) const { +stdAc::state_t IRSharpAc::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::SHARP_AC; result.model = getModel(); result.power = getPower(); @@ -845,7 +843,8 @@ stdAc::state_t IRSharpAc::toCommon(void) const { result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.turbo = getTurbo(); - result.swingv = toCommonSwingV(getSwingV(), result.mode); + if (getSwingV() != kSharpAcSwingVIgnore) + result.swingv = toCommonSwingV(getSwingV(), result.mode); result.filter = _.Ion; result.econo = getEconoToggle(); result.light = getLightToggle(); @@ -889,21 +888,28 @@ String IRSharpAc::toString(void) const { kSharpAcFanAuto, kSharpAcFanAuto, kSharpAcFanMed); } - result += addSwingVToString( - getSwingV(), kSharpAcSwingVAuto, - // Coanda means Highest when in Cool mode. - (mode == kSharpAcCool) ? kSharpAcSwingVCoanda : kSharpAcSwingVAuto, - kSharpAcSwingVHigh, - kSharpAcSwingVAuto, // Upper Middle is unused - kSharpAcSwingVMid, - kSharpAcSwingVAuto, // Lower Middle is unused - kSharpAcSwingVLow, - kSharpAcSwingVCoanda, - kSharpAcSwingVOff, - // Below are unused. - kSharpAcSwingVAuto, - kSharpAcSwingVAuto, - kSharpAcSwingVAuto); + if (getSwingV() == kSharpAcSwingVIgnore) { + result += addIntToString(kSharpAcSwingVIgnore, kSwingVStr); + result += kSpaceLBraceStr; + result += kNAStr; + result += ')'; + } else { + result += addSwingVToString( + getSwingV(), 0xFF, + // Coanda means Highest when in Cool mode. + (mode == kSharpAcCool) ? kSharpAcSwingVCoanda : kSharpAcSwingVToggle, + kSharpAcSwingVHigh, + 0xFF, // Upper Middle is unused + kSharpAcSwingVMid, + 0xFF, // Lower Middle is unused + kSharpAcSwingVLow, + kSharpAcSwingVCoanda, + kSharpAcSwingVOff, + // Below are unused. + kSharpAcSwingVToggle, + 0xFF, + 0xFF); + } result += addBoolToString(getTurbo(), kTurboStr); result += addBoolToString(_.Ion, kIonStr); switch (model) { diff --git a/src/ir_Sharp.h b/src/ir_Sharp.h index 443ed62ac..1cea6cdb3 100644 --- a/src/ir_Sharp.h +++ b/src/ir_Sharp.h @@ -123,15 +123,15 @@ const uint8_t kSharpAcOffTimerType = 0b0; const uint8_t kSharpAcOnTimerType = 0b1; // Ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1260213 -const uint8_t kSharpAcSwingVOff = 0b000; // Stop swinging. +const uint8_t kSharpAcSwingVIgnore = 0b000; // Don't change the swing setting. const uint8_t kSharpAcSwingVHigh = 0b001; // 0° down aka Coanda (Cool) -const uint8_t kSharpAcSwingVOff1 = 0b010; // Off, but go to last fixed pos. +const uint8_t kSharpAcSwingVOff = 0b010; // Stop & Go to last fixed pos. const uint8_t kSharpAcSwingVMid = 0b011; // 30° down const uint8_t kSharpAcSwingVLow = 0b100; // 45° down -const uint8_t kSharpAcSwingVOff2 = 0b101; // Off, but go to last fixed pos. +const uint8_t kSharpAcSwingVLast = 0b101; // Same as kSharpAcSwingVOff. const uint8_t kSharpAcSwingVCoanda = 0b110; // 0° down (Cool), 75° down (Heat) const uint8_t kSharpAcSwingVLowest = kSharpAcSwingVCoanda; -const uint8_t kSharpAcSwingVAuto = 0b111; // Constant swinging +const uint8_t kSharpAcSwingVToggle = 0b111; // Toggle Constant swinging on/off. const uint8_t kSharpAcSpecialPower = 0x00; const uint8_t kSharpAcSpecialTurbo = 0x01; @@ -204,7 +204,7 @@ class IRSharpAc { stdAc::swingv_t toCommonSwingV( const uint8_t pos, const stdAc::opmode_t mode = stdAc::opmode_t::kHeat) const; - stdAc::state_t toCommon(void) const; + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; String toString(void) const; #ifndef UNIT_TEST diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 217486cac..91f45f558 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1610,7 +1610,7 @@ TEST(TestIRac, Sharp) { IRrecv capture(kGpioUnused); char expected[] = "Model: 1 (A907), Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 3 (Medium), " - "Swing(V): 7 (Auto), Turbo: Off, Ion: On, Econo: -, Clean: Off"; + "Swing(V): 7 (Swing), Turbo: Off, Ion: On, Econo: -, Clean: Off"; ac.begin(); irac.sharp(&ac, @@ -1621,6 +1621,7 @@ TEST(TestIRac, Sharp) { 28, // Celsius stdAc::fanspeed_t::kMedium, // Fan speed stdAc::swingv_t::kAuto, // Vertical swing + stdAc::swingv_t::kOff, // Previous Vertical swing false, // Turbo false, // Light true, // Filter (Ion) diff --git a/test/ir_Sharp_test.cpp b/test/ir_Sharp_test.cpp index 0c0f9ab69..a2acafb9c 100644 --- a/test/ir_Sharp_test.cpp +++ b/test/ir_Sharp_test.cpp @@ -411,7 +411,7 @@ TEST(TestDecodeSharpAc, RealExample) { EXPECT_EQ( "Model: 1 (A907), " "Power: On, Mode: 2 (Cool), Temp: 27C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -561,7 +561,7 @@ TEST(TestSharpAcClass, OperatingMode) { // Check toString() says Fan rather than Auto. EXPECT_EQ( "Model: 2 (A705), Power: Off, Mode: 0 (Fan), Temp: 15C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); } @@ -615,7 +615,7 @@ TEST(TestSharpAcClass, ReconstructKnownState) { EXPECT_STATE_EQ(on_auto_auto, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); @@ -629,7 +629,7 @@ TEST(TestSharpAcClass, ReconstructKnownState) { ac.setTemp(28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); EXPECT_STATE_EQ(cool_auto_28, ac.getRaw(), kSharpAcBits); @@ -648,7 +648,7 @@ TEST(TestSharpAcClass, KnownStates) { EXPECT_EQ( "Model: 1 (A907), " "Power: Off, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t on_auto_auto[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0x00, 0x11, 0x20, 0x00, 0x08, 0x80, 0x00, 0xE0, @@ -657,7 +657,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(on_auto_auto); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Power: On, Mode: 0 (Auto), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_auto_28[kSharpAcStateLength] = { @@ -667,7 +667,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_auto_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 2 (Auto), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan1_28[kSharpAcStateLength] = { @@ -677,7 +677,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan1_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 4 (Low), Swing(V): 0 (Off), " + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 4 (Low), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan2_28[kSharpAcStateLength] = { @@ -688,7 +688,7 @@ TEST(TestSharpAcClass, KnownStates) { EXPECT_EQ( "Model: 1 (A907), " "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 3 (Medium), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan3_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x52, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -698,7 +698,7 @@ TEST(TestSharpAcClass, KnownStates) { EXPECT_EQ( "Model: 1 (A907), " "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 5 (UNKNOWN), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan4_28[kSharpAcStateLength] = { 0xAA, 0x5A, 0xCF, 0x10, 0xCD, 0x31, 0x72, 0x00, 0x08, 0x80, 0x05, 0xE0, @@ -707,7 +707,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan4_28); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (Off), " + "Power: On, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); uint8_t cool_fan4_28_ion_on[kSharpAcStateLength] = { @@ -717,7 +717,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(cool_fan4_28_ion_on); EXPECT_EQ( "Model: 1 (A907), " - "Power: -, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (Off), " + "Power: -, Mode: 2 (Cool), Temp: 28C, Fan: 7 (High), Swing(V): 0 (N/A), " "Turbo: Off, Ion: On, Econo: -, Clean: Off", ac.toString()); /* Unsupported / Not yet reverse engineered. @@ -735,7 +735,7 @@ TEST(TestSharpAcClass, KnownStates) { ac.setRaw(dry_auto); EXPECT_EQ( "Model: 1 (A907), " - "Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (Off), " + "Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), Swing(V): 0 (N/A), " "Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); } @@ -747,6 +747,7 @@ TEST(TestSharpAcClass, toCommon) { ac.setMode(kSharpAcCool); ac.setTemp(20); ac.setFan(kSharpAcFanMax); + ac.setSwingV(kSharpAcSwingVOff); // Now test it. ASSERT_EQ(decode_type_t::SHARP_AC, ac.toCommon().protocol); ASSERT_TRUE(ac.toCommon().power); @@ -755,8 +756,8 @@ TEST(TestSharpAcClass, toCommon) { ASSERT_EQ(stdAc::opmode_t::kCool, ac.toCommon().mode); ASSERT_EQ(stdAc::fanspeed_t::kMax, ac.toCommon().fanspeed); ASSERT_EQ(sharp_ac_remote_model_t::A705, ac.toCommon().model); - // Unsupported. ASSERT_EQ(stdAc::swingv_t::kOff, ac.toCommon().swingv); + // Unsupported. ASSERT_EQ(stdAc::swingh_t::kOff, ac.toCommon().swingh); ASSERT_FALSE(ac.toCommon().turbo); ASSERT_FALSE(ac.toCommon().quiet); @@ -869,7 +870,7 @@ TEST(TestSharpAcClass, Turbo) { EXPECT_EQ( "Model: 3 (A903), " "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: On, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: On, Ion: On, Light: -, Clean: Off", ac.toString()); ac.setRaw(off_state); @@ -877,7 +878,7 @@ TEST(TestSharpAcClass, Turbo) { EXPECT_EQ( "Model: 3 (A903), " "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); } @@ -908,8 +909,8 @@ TEST(TestSharpAcClass, Swings) { EXPECT_FALSE(ac.getSwingToggle()); // Vertical - ac.setSwingV(kSharpAcSwingVAuto); - EXPECT_EQ(kSharpAcSwingVAuto, ac.getSwingV()); + ac.setSwingV(kSharpAcSwingVToggle); + EXPECT_EQ(kSharpAcSwingVToggle, ac.getSwingV()); EXPECT_TRUE(ac.getSwingToggle()); ac.setSwingV(kSharpAcSwingVHigh); @@ -927,8 +928,8 @@ TEST(TestSharpAcClass, Swings) { EXPECT_EQ(kSharpAcSwingVLow, ac.getSwingV()); EXPECT_FALSE(ac.getSwingToggle()); - ac.setSwingV(kSharpAcSwingVOff); - EXPECT_EQ(kSharpAcSwingVOff, ac.getSwingV()); + ac.setSwingV(kSharpAcSwingVIgnore); + EXPECT_EQ(kSharpAcSwingVIgnore, ac.getSwingV()); EXPECT_FALSE(ac.getSwingToggle()); // Lowest/Coanda only works in Heat mode. @@ -1069,7 +1070,7 @@ TEST(TestSharpAcClass, Timers) { EXPECT_TRUE(ac.isPowerSpecial()); EXPECT_EQ( "Model: 3 (A903), " - "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Swing(V): 0 (Off), " + "Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), Swing(V): 0 (N/A), " "Turbo: Off, Ion: On, Light: -, Clean: Off, Off Timer: 08:30", ac.toString()); @@ -1084,7 +1085,7 @@ TEST(TestSharpAcClass, Timers) { EXPECT_TRUE(ac.isPowerSpecial()); EXPECT_EQ( "Model: 3 (A903), Power: -, Mode: 2 (Cool), Temp: 21C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off, " + "Swing(V): 0 (N/A), Turbo: Off, Ion: On, Light: -, Clean: Off, " "On Timer: 12:00", ac.toString()); } @@ -1122,13 +1123,13 @@ TEST(TestSharpAcClass, Clean) { EXPECT_TRUE(ac.getClean()); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: On", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Light: -, Clean: On", ac.toString()); ac.setRaw(clean_off_state); EXPECT_FALSE(ac.getClean()); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); // Try constructing the clean on state. @@ -1148,13 +1149,13 @@ TEST(TestSharpAcClass, Clean) { ac.setPower(false); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // Clean ON ac.setClean(true); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: On", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: On", ac.toString()); // Clean OFF (state is identical to `off_msg`). // i.e. It just clears the clean settings & turns off the device. @@ -1162,25 +1163,25 @@ TEST(TestSharpAcClass, Clean) { ac.setPower(false, true); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // Clean ON ac.setClean(true); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 3 (Dry), Temp: 15C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: On", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: On", ac.toString()); // AC OFF ac.off(); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); // AC ON (Mode Cool, Temp 25, Ion OFF, Fan 7) ac.on(); EXPECT_EQ( "Model: 1 (A907), Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 7 (High), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); } @@ -1190,7 +1191,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.stateReset(); EXPECT_EQ( "Model: 1 (A907), Power: Off, Mode: 0 (Auto), Temp: 15C, Fan: 0 (UNKNOWN), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Econo: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Econo: -, Clean: Off", ac.toString()); const uint8_t issue1309_on[13] = { @@ -1199,7 +1200,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.setRaw(issue1309_on); EXPECT_EQ( "Model: 2 (A705), Power: On, Mode: 2 (Cool), Temp: 16C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); EXPECT_STATE_EQ(issue1309_on, ac.getRaw(), kSharpAcBits); @@ -1211,7 +1212,7 @@ TEST(TestSharpAcClass, Issue1309) { ac.on(); EXPECT_EQ( "Model: 2 (A705), Power: On, Mode: 2 (Cool), Temp: 16C, Fan: 2 (Auto), " - "Swing(V): 0 (Off), Turbo: Off, Ion: Off, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: Off, Light: -, Clean: Off", ac.toString()); } @@ -1264,13 +1265,13 @@ TEST(TestSharpAcClass, Issue1387Power) { EXPECT_STATE_EQ(real_off, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 3 (A903), Power: Off, Mode: 2 (Cool), Temp: 27C, Fan: 3 (Low), " - "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); // Create the same off state. ac.setPower(true, ac.getPower()); EXPECT_STATE_EQ(real_on, ac.getRaw(), kSharpAcBits); EXPECT_EQ( "Model: 3 (A903), Power: On, Mode: 2 (Cool), Temp: 27C, Fan: 3 (Low), " - "Swing(V): 0 (Off), Turbo: Off, Ion: On, Light: -, Clean: Off", + "Swing(V): 0 (N/A), Turbo: Off, Ion: On, Light: -, Clean: Off", ac.toString()); } From 86e08a76a43c285e23c429fa1390da1ec599b118 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sun, 5 Sep 2021 08:43:08 +1000 Subject: [PATCH 4/4] Update comments. Ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1280573 --- src/ir_Sharp.cpp | 4 ++++ src/ir_Sharp.h | 13 ++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/ir_Sharp.cpp b/src/ir_Sharp.cpp index 190fe1667..77d282d4a 100644 --- a/src/ir_Sharp.cpp +++ b/src/ir_Sharp.cpp @@ -552,6 +552,10 @@ uint8_t IRSharpAc::getSwingV(void) const { return _.Swing; } /// Set the Vertical Swing setting of the A/C. /// @note Some positions may not work on all models. /// @param[in] position The desired position/setting. +/// @note `setSwingV(kSharpAcSwingVLowest)` will only allow the Lowest setting +/// in Heat mode, it will default to `kSharpAcSwingVLow` otherwise. +/// If you want to set this value in other modes e.g. Cool, you must +/// use `setSwingV`s optional `force` parameter. /// @param[in] force Do we override the safety checks and just do it? void IRSharpAc::setSwingV(const uint8_t position, const bool force) { switch (position) { diff --git a/src/ir_Sharp.h b/src/ir_Sharp.h index 1cea6cdb3..b3be534e7 100644 --- a/src/ir_Sharp.h +++ b/src/ir_Sharp.h @@ -124,13 +124,20 @@ const uint8_t kSharpAcOnTimerType = 0b1; // Ref: https://github.com/crankyoldgit/IRremoteESP8266/discussions/1590#discussioncomment-1260213 const uint8_t kSharpAcSwingVIgnore = 0b000; // Don't change the swing setting. -const uint8_t kSharpAcSwingVHigh = 0b001; // 0° down aka Coanda (Cool) +const uint8_t kSharpAcSwingVHigh = 0b001; // 0° down. Similar to Cool Coanda. const uint8_t kSharpAcSwingVOff = 0b010; // Stop & Go to last fixed pos. const uint8_t kSharpAcSwingVMid = 0b011; // 30° down const uint8_t kSharpAcSwingVLow = 0b100; // 45° down const uint8_t kSharpAcSwingVLast = 0b101; // Same as kSharpAcSwingVOff. -const uint8_t kSharpAcSwingVCoanda = 0b110; // 0° down (Cool), 75° down (Heat) -const uint8_t kSharpAcSwingVLowest = kSharpAcSwingVCoanda; +// Toggles between last fixed pos & either 75° down (Heat) or 0° down (Cool) +// i.e. alternate between last pos <-> 75° down if in Heat mode, AND +// alternate between last pos <-> 0° down if in Cool mode. +// Note: `setSwingV(kSharpAcSwingVLowest)` will only allow the Lowest setting in +// Heat mode, it will default to `kSharpAcSwingVLow` otherwise. +// If you want to set this value in other modes e.g. Cool, you must +// use `setSwingV`s optional `force` parameter. +const uint8_t kSharpAcSwingVLowest = 0b110; +const uint8_t kSharpAcSwingVCoanda = kSharpAcSwingVLowest; const uint8_t kSharpAcSwingVToggle = 0b111; // Toggle Constant swinging on/off. const uint8_t kSharpAcSpecialPower = 0x00;