diff --git a/src/IRac.cpp b/src/IRac.cpp index 3d13e42b6..fdb74a1ca 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1333,20 +1333,23 @@ void IRac::kelvinator(IRKelvinatorAC *ac, /// @param[in] mode The operation mode setting. /// @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] light Turn on the LED/Display mode. void IRac::lg(IRLgAc *ac, const lg_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, - const float degrees, const stdAc::fanspeed_t fan) { + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool light) { ac->begin(); ac->setModel(model); ac->setPower(on); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); ac->setFan(ac->convertFan(fan)); - // No Vertical swing setting available. + ac->setSwingV(ac->convertSwingV(swingv)); // No Horizontal swing setting available. // No Quiet setting available. // No Turbo setting available. - // No Light setting available. + ac->setLight(light); // No Filter setting available. // No Clean setting available. // No Beep setting available. @@ -2638,7 +2641,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { { IRLgAc ac(_pin, _inverted, _modulation); lg(&ac, (lg_ac_remote_model_t)send.model, send.power, send.mode, - send.degrees, send.fanspeed); + send.degrees, send.fanspeed, send.swingv, send.light); break; } #endif // SEND_LG diff --git a/src/IRac.h b/src/IRac.h index 8f765ccbe..c9cb2cdb8 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -307,7 +307,8 @@ void electra(IRElectraAc *ac, #if SEND_LG void lg(IRLgAc *ac, const lg_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, - const float degrees, const stdAc::fanspeed_t fan); + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool light); #endif // SEND_LG #if SEND_MIDEA void midea(IRMideaAC *ac, diff --git a/src/ir_LG.cpp b/src/ir_LG.cpp index 3e2f04119..f2e04e9dd 100644 --- a/src/ir_LG.cpp +++ b/src/ir_LG.cpp @@ -21,6 +21,7 @@ using irutils::addModeToString; using irutils::addModelToString; using irutils::addFanToString; using irutils::addTempToString; +using irutils::addSwingVToString; // Constants @@ -222,6 +223,8 @@ void IRLgAc::stateReset(void) { setRaw(kLgAcOffCommand); setModel(lg_ac_remote_model_t::GE6711AR2853M); _light = true; + _swingv = kLgAcSwingVOff; + _swingv_prev = _swingv; } /// Set up hardware to be able to send a message. @@ -233,11 +236,22 @@ void IRLgAc::begin(void) { _irsend.begin(); } void IRLgAc::send(const uint16_t repeat) { if (getPower()) { _irsend.send(_protocol, getRaw(), kLgBits, repeat); - // Any "normal" command sent will always turn the light on, thus we only - // send it when we want it off. Light control only available on some models. - // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1513#issuecomment-877283080 - if (!_light && getModel() == lg_ac_remote_model_t::AKB74955603) - _irsend.send(_protocol, kLgAcLightToggle, kLgBits, repeat); + // Some models have extra/special settings & controls + switch (getModel()) { + case lg_ac_remote_model_t::AKB74955603: + // Only send the swing setting if we need to. + if (_swingv != _swingv_prev) { + _irsend.send(_protocol, _swingv, kLgBits, repeat); + _swingv_prev = _swingv; + } + // Any "normal" command sent will always turn the light on, thus we only + // send it when we want it off. Must be sent last! + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1513#issuecomment-877283080 + if (!_light) _irsend.send(_protocol, kLgAcLightToggle, kLgBits, repeat); + break; + default: + break; + } } else { // Always send the special Off command if the power is set to off. // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1008#issuecomment-570763580 @@ -254,6 +268,7 @@ bool IRLgAc::_isNormal(void) const { case kLgAcLightToggle: return false; } + if (isSwingV()) return false; return true; } @@ -282,8 +297,10 @@ lg_ac_remote_model_t IRLgAc::getModel(void) const { /// Check if the stored code must belong to a AKB74955603 model. /// @return true, if it is AKB74955603 message. Otherwise, false. +/// @note Internal use only. bool IRLgAc::_isAKB74955603(void) const { - return ((_.raw & kLgAcAKB74955603DetectionMask) || _.raw == kLgAcLightToggle); + return ((_.raw & kLgAcAKB74955603DetectionMask) || isSwingV() || + isLightToggle()); } /// Get a copy of the internal state/code for this protocol. @@ -314,6 +331,7 @@ void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) { if (_isAKB74955603()) setModel(lg_ac_remote_model_t::AKB74955603); _temp = 15; // Ensure there is a "sane" previous temp. _temp = getTemp(); + if (isSwingV()) _swingv = new_code; } /// Calculate the checksum for a given state. @@ -378,9 +396,7 @@ bool IRLgAc::isLightToggle(void) const { return _.raw == kLgAcLightToggle; } /// Set the temperature. /// @param[in] value The native temperature. /// @note Internal use only. -inline void IRLgAc::_setTemp(const uint8_t value) { - _.Temp = value; -} +inline void IRLgAc::_setTemp(const uint8_t value) { _.Temp = value; } /// Set the temperature. /// @param[in] degrees The temperature in degrees celsius. @@ -458,6 +474,31 @@ void IRLgAc::setMode(const uint8_t mode) { } } +/// Check if the stored code is a Swing message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingV(void) const { + return (_.raw >> 12) == kLgAcSwingSignature; +} + +/// Set the Vertical Swing mode of the A/C. +/// @param[in] position The position/mode to set the vanes to. +void IRLgAc::setSwingV(const uint32_t position) { + // Is it a valid position code? + if (position == kLgAcSwingVOff || + toCommonSwingV(position) != stdAc::swingv_t::kOff) { + if (position <= 0xFF) { // It's a short code, convert it. + _swingv = (kLgAcSwingSignature << 8 | position) << 4; + _swingv |= calcChecksum(_swingv); + } else { + _swingv = position; + } + } +} + +/// Get the Vertical Swing position setting of the A/C. +/// @return The native position/mode. +uint32_t IRLgAc::getSwingV(void) const { return _swingv; } + /// 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. @@ -513,6 +554,44 @@ stdAc::fanspeed_t IRLgAc::toCommonFanSpeed(const uint8_t speed) { } } +/// Convert a stdAc::swingv_t enum into it's native setting. +/// @param[in] swingv The enum to be converted. +/// @return The native equivalent of the enum. +uint32_t IRLgAc::convertSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHighest: return kLgAcSwingVHighest; + case stdAc::swingv_t::kHigh: return kLgAcSwingVHigh; + case stdAc::swingv_t::kMiddle: return kLgAcSwingVMiddle; + case stdAc::swingv_t::kLow: return kLgAcSwingVLow; + case stdAc::swingv_t::kLowest: return kLgAcSwingVLowest; + case stdAc::swingv_t::kAuto: return kLgAcSwingVSwing; + default: return kLgAcSwingVOff; + } +} + +/// Convert a native Vertical Swing into its stdAc equivalent. +/// @param[in] code The native code to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::swingv_t IRLgAc::toCommonSwingV(const uint32_t code) { + switch (code) { + case kLgAcSwingVHighest_Short: + case kLgAcSwingVHighest: return stdAc::swingv_t::kHighest; + case kLgAcSwingVHigh_Short: + case kLgAcSwingVHigh: return stdAc::swingv_t::kHigh; + case kLgAcSwingVUpperMiddle_Short: + case kLgAcSwingVUpperMiddle: + case kLgAcSwingVMiddle_Short: + case kLgAcSwingVMiddle: return stdAc::swingv_t::kMiddle; + case kLgAcSwingVLow_Short: + case kLgAcSwingVLow: return stdAc::swingv_t::kLow; + case kLgAcSwingVLowest_Short: + case kLgAcSwingVLowest: return stdAc::swingv_t::kLowest; + case kLgAcSwingVSwing_Short: + case kLgAcSwingVSwing: 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. @@ -526,6 +605,7 @@ stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const { // there is no previous state. // e.g. Any setting that toggles should probably go here. result.light = true; + result.swingv = toCommonSwingV(getSwingV()); } result.protocol = _protocol; result.model = getModel(); @@ -535,8 +615,8 @@ stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const { result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.light = isLightToggle() ? !result.light : _light; + if (isSwingV()) result.swingv = toCommonSwingV(getSwingV()); // Not supported. - result.swingv = stdAc::swingv_t::kOff; result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.turbo = false; @@ -569,6 +649,19 @@ String IRLgAc::toString(void) const { } else { if (isOffCommand()) result += addBoolToString(false, kPowerStr); if (isLightToggle()) result += addBoolToString(true, kLightToggleStr); + if (isSwingV()) + result += addSwingVToString((uint8_t)(_swingv >> 4), + 0, // No Auto, See "swing". Unused + kLgAcSwingVHighest_Short, + kLgAcSwingVHigh_Short, + kLgAcSwingVUpperMiddle_Short, + kLgAcSwingVMiddle_Short, + 0, // Unused + kLgAcSwingVLow_Short, + kLgAcSwingVLowest_Short, + kLgAcSwingVOff_Short, + kLgAcSwingVSwing_Short, + 0, 0); } return result; } diff --git a/src/ir_LG.h b/src/ir_LG.h index 2cb788f2f..009a47d18 100644 --- a/src/ir_LG.h +++ b/src/ir_LG.h @@ -66,8 +66,28 @@ const uint8_t kLgAcPowerOff = 3; // 0b11 const uint8_t kLgAcPowerOn = 0; // 0b00 const uint8_t kLgAcSignature = 0x88; -const uint32_t kLgAcOffCommand = 0x88C0051; -const uint32_t kLgAcLightToggle = 0x88C00A6; +const uint32_t kLgAcOffCommand = 0x88C0051; +const uint32_t kLgAcLightToggle = 0x88C00A6; + +const uint32_t kLgAcSwingSignature = 0x8813; +const uint32_t kLgAcSwingVLowest = 0x8813048; +const uint32_t kLgAcSwingVLow = 0x8813059; +const uint32_t kLgAcSwingVMiddle = 0x881306A; +const uint32_t kLgAcSwingVUpperMiddle = 0x881307B; +const uint32_t kLgAcSwingVHigh = 0x881308C; +const uint32_t kLgAcSwingVHighest = 0x881309D; +const uint32_t kLgAcSwingVSwing = 0x8813149; +const uint32_t kLgAcSwingVAuto = kLgAcSwingVSwing; +const uint32_t kLgAcSwingVOff = 0x881315A; +const uint8_t kLgAcSwingVLowest_Short = 0x04; +const uint8_t kLgAcSwingVLow_Short = 0x05; +const uint8_t kLgAcSwingVMiddle_Short = 0x06; +const uint8_t kLgAcSwingVUpperMiddle_Short = 0x07; +const uint8_t kLgAcSwingVHigh_Short = 0x08; +const uint8_t kLgAcSwingVHighest_Short = 0x09; +const uint8_t kLgAcSwingVSwing_Short = 0x14; +const uint8_t kLgAcSwingVAuto_Short = kLgAcSwingVSwing_Short; +const uint8_t kLgAcSwingVOff_Short = 0x15; // Classes /// Class for handling detailed LG A/C messages. @@ -102,13 +122,18 @@ class IRLgAc { void setLight(const bool on); bool getLight(void) const; bool isLightToggle(void) const; + bool isSwingV(void) const; + void setSwingV(const uint32_t position); + uint32_t getSwingV(void) const; uint32_t getRaw(void); void setRaw(const uint32_t new_code, const decode_type_t protocol = decode_type_t::UNKNOWN); static uint8_t convertMode(const stdAc::opmode_t mode); static stdAc::opmode_t toCommonMode(const uint8_t mode); static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint32_t code); static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint32_t convertSwingV(const stdAc::swingv_t swingv); stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const; String toString(void) const; void setModel(const lg_ac_remote_model_t model); @@ -125,6 +150,8 @@ class IRLgAc { LGProtocol _; uint8_t _temp; bool _light; + uint32_t _swingv; + uint32_t _swingv_prev; decode_type_t _protocol; ///< Protocol version lg_ac_remote_model_t _model; ///< Model type void checksum(void); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 74e530d7c..47ecb8b01 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1013,7 +1013,9 @@ TEST(TestIRac, LG) { true, // Power stdAc::opmode_t::kDry, // Mode 27, // Degrees C - stdAc::fanspeed_t::kMedium); // Fan speed + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kLow, // Vertical swing + true); // Light ASSERT_EQ(expected, ac.toString()); ac._irsend.makeDecodeResult(); @@ -1021,10 +1023,52 @@ TEST(TestIRac, LG) { ASSERT_EQ(LG, ac._irsend.capture.decode_type); ASSERT_EQ(kLgBits, ac._irsend.capture.bits); ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + // There should only be a single message. + ASSERT_EQ(61, ac._irsend.capture.rawlen); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); } +TEST(TestIRac, LG2) { + IRLgAc ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + char expected[] = + "Model: 3 (AKB74955603), " + "Power: On, Mode: 1 (Dry), Temp: 27C, Fan: 9 (Low)"; + + ac.begin(); + irac.lg(&ac, + lg_ac_remote_model_t::AKB74955603, // Model + true, // Power + stdAc::opmode_t::kDry, // Mode + 27, // Degrees C + stdAc::fanspeed_t::kLow, // Fan speed + stdAc::swingv_t::kLow, // Vertical swing + false); // Light + + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + ASSERT_EQ(181, ac._irsend.capture.rawlen); // We expect three messages. + // Message #1 + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); + // Message #2 - SwingV Low + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 61)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(kLgAcSwingVLow, ac._irsend.capture.value); + // Message #3 - Light Toggle + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 121)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(kLgAcLightToggle, ac._irsend.capture.value); +} + TEST(TestIRac, Midea) { IRMideaAC ac(kGpioUnused); IRac irac(kGpioUnused); diff --git a/test/ir_LG_test.cpp b/test/ir_LG_test.cpp index 40eb45c05..a06c6b09a 100644 --- a/test/ir_LG_test.cpp +++ b/test/ir_LG_test.cpp @@ -950,6 +950,10 @@ TEST(TestIRLgAcClass, DetectAKB74955603) { ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); + + ac.stateReset(); + ac.setRaw(0x881306A); + EXPECT_EQ(lg_ac_remote_model_t::AKB74955603, ac.getModel()); } TEST(TestIRLgAcClass, Light) { @@ -984,3 +988,47 @@ TEST(TestIRLgAcClass, Light) { ASSERT_EQ("Model: 3 (AKB74955603), Light Toggle: On", IRAcUtils::resultAcToString(&ac._irsend.capture)); } + +TEST(TestIRLgAcClass, SwingV) { + IRLgAc ac(kGpioUnused); + IRrecv capture(kGpioUnused); + ac.begin(); + + EXPECT_EQ(kLgAcSwingVOff, ac.getSwingV()); + + // Ref: https://docs.google.com/spreadsheets/d/1zF0FI2ENvbLdk4zaWBY9ZYVM3MB_4oxro9wCM7ETX4Y/edit#gid=1912869597&range=D2:E9 + ac.setRaw(0x881306A); + EXPECT_TRUE(ac.isSwingV()); + const char expected_middle[] = + "Model: 3 (AKB74955603), Swing(V): 6 (Middle)"; + EXPECT_EQ(expected_middle, ac.toString()); + ac.setSwingV(kLgAcSwingVHigh); + EXPECT_EQ(kLgAcSwingVHigh, ac.getSwingV()); + ac.setSwingV(0x880A396); // Non SwingV code. + EXPECT_EQ(kLgAcSwingVHigh, ac.getSwingV()); // Last setting. + + // Test sending via the class method. + ac.stateReset(); + ac.setRaw(0x880A396); // A known normal state. + const char expected[] = + "Model: 3 (AKB74955603), Power: On, Mode: 2 (Fan), Temp: 18C, " + "Fan: 9 (Low)"; + ac.setSwingV(kLgAcSwingVMiddle_Short); + EXPECT_EQ(kLgAcSwingVMiddle, ac.getSwingV()); + + ac._irsend.reset(); + ac.send(); + ac._irsend.makeDecodeResult(); + // First message should be normal. + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); // Not "LG" + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + // The next should be a middle swing. + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 61)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); // Not "LG" + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + EXPECT_EQ(kLgAcSwingVMiddle, ac._irsend.capture.value); + ASSERT_EQ(expected_middle, + IRAcUtils::resultAcToString(&ac._irsend.capture)); +}