From 8bd055583e8a45231cd43d6da10cac1a473eb46a Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 3 Aug 2021 16:28:49 +1000 Subject: [PATCH 1/5] [LG] Add support for AKB73757604 model * Support for Vertical Swing on multiple vanes. * Support for Horizontal Swing. * Model detection. * Unit tests added & updated. Fixes #1531 --- src/IRac.cpp | 14 +++- src/IRac.h | 2 +- src/IRsend.h | 1 + src/IRtext.cpp | 1 + src/IRtext.h | 1 + src/IRutils.cpp | 5 +- src/ir_LG.cpp | 182 ++++++++++++++++++++++++++++++++++++++---- src/ir_LG.h | 36 ++++++++- src/locale/defaults.h | 3 + test/IRac_test.cpp | 60 ++++++++++++++ test/ir_LG_test.cpp | 35 ++++++++ 11 files changed, 314 insertions(+), 26 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 15f897d78..f5bfa900c 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1340,7 +1340,7 @@ 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 stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, - const bool light) { + const stdAc::swingh_t swingh, const bool light) { ac->begin(); ac->setModel(model); ac->setPower(on); @@ -1348,9 +1348,12 @@ void IRac::lg(IRLgAc *ac, const lg_ac_remote_model_t model, ac->setTemp(degrees); ac->setFan(ac->convertFan(fan)); ac->setSwingV(ac->convertSwingV(swingv_prev)); - ac->updateSwingVPrev(); + ac->updateSwingPrev(); ac->setSwingV(ac->convertSwingV(swingv)); - // No Horizontal swing setting available. + const uint8_t pos = ac->convertVaneSwingV(swingv); + for (uint8_t vane = 0; vane < kLgAcSwingVMaxVanes; vane++) + ac->setVaneSwingV(vane, pos); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); // No Quiet setting available. // No Turbo setting available. ac->setLight(light); @@ -2650,7 +2653,8 @@ 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.swingv, prev_swingv, send.light); + send.degrees, send.fanspeed, send.swingv, prev_swingv, send.swingh, + send.light); break; } #endif // SEND_LG @@ -3071,6 +3075,8 @@ int16_t IRac::strToModel(const char *str, const int16_t def) { return lg_ac_remote_model_t::AKB75215403; } else if (!strcasecmp(str, "AKB74955603")) { return lg_ac_remote_model_t::AKB74955603; + } else if (!strcasecmp(str, "AKB73757604")) { + return lg_ac_remote_model_t::AKB73757604; // Panasonic A/C families } else if (!strcasecmp(str, "LKE") || !strcasecmp(str, "PANASONICLKE")) { return panasonic_ac_remote_model_t::kPanasonicLke; diff --git a/src/IRac.h b/src/IRac.h index cda095873..d47778930 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -309,7 +309,7 @@ void electra(IRElectraAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingv_t swingv_prev, - const bool light); + const stdAc::swingh_t swingh, const bool light); #endif // SEND_LG #if SEND_MIDEA void midea(IRMideaAC *ac, diff --git a/src/IRsend.h b/src/IRsend.h index 31a78c5b9..78d80628a 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -177,6 +177,7 @@ enum lg_ac_remote_model_t { GE6711AR2853M = 1, // (1) LG 28-bit Protocol (default) AKB75215403, // (2) LG2 28-bit Protocol AKB74955603, // (3) LG2 28-bit Protocol variant + AKB73757604, // (4) LG2 Variant of AKB74955603 }; diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 91233ad80..48e4e29b9 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -99,6 +99,7 @@ const PROGMEM char* k6thSenseStr = D_STR_6THSENSE; ///< "6th Sense" const PROGMEM char* kTypeStr = D_STR_TYPE; ///< "Type" const PROGMEM char* kSpecialStr = D_STR_SPECIAL; ///< "Special" const PROGMEM char* kIdStr = D_STR_ID; ///< "Id" / Device Identifier +const PROGMEM char* kVaneStr = D_STR_VANE; ///< "Vane" const PROGMEM char* kAutoStr = D_STR_AUTO; ///< "Auto" const PROGMEM char* kAutomaticStr = D_STR_AUTOMATIC; ///< "Automatic" diff --git a/src/IRtext.h b/src/IRtext.h index d991276cd..cb2ba5a9a 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -156,6 +156,7 @@ extern const char* kTypeStr; extern const char* kUnknownStr; extern const char* kUpperStr; extern const char* kUpStr; +extern const char* kVaneStr; extern const char* kWallStr; extern const char* kWeeklyTimerStr; extern const char* kWideStr; diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 56689ef2e..9815a37b6 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -578,8 +578,9 @@ namespace irutils { case decode_type_t::LG2: switch (model) { case lg_ac_remote_model_t::GE6711AR2853M: return F("GE6711AR2853M"); - case lg_ac_remote_model_t::AKB75215403: return F("AKB75215403"); - case lg_ac_remote_model_t::AKB74955603: return F("AKB74955603"); + case lg_ac_remote_model_t::AKB75215403: return F("AKB75215403"); + case lg_ac_remote_model_t::AKB74955603: return F("AKB74955603"); + case lg_ac_remote_model_t::AKB73757604: return F("AKB73757604"); default: return kUnknownStr; } break; diff --git a/src/ir_LG.cpp b/src/ir_LG.cpp index 4c3a9f9f5..5c73f9ca6 100644 --- a/src/ir_LG.cpp +++ b/src/ir_LG.cpp @@ -23,6 +23,7 @@ using irutils::addModelToString; using irutils::addFanToString; using irutils::addTempToString; using irutils::addSwingVToString; +using irutils::addIntToString; // Constants @@ -225,7 +226,10 @@ void IRLgAc::stateReset(void) { setModel(lg_ac_remote_model_t::GE6711AR2853M); _light = true; _swingv = kLgAcSwingVOff; - updateSwingVPrev(); + _swingh = false; + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) + _vaneswingv[i] = 0; // Reset to an unused value. + updateSwingPrev(); } /// Set up hardware to be able to send a message. @@ -241,18 +245,28 @@ void IRLgAc::send(const uint16_t repeat) { switch (getModel()) { case lg_ac_remote_model_t::AKB74955603: // Only send the swing setting if we need to. - if (_swingv != _swingv_prev) { + if (_swingv != _swingv_prev) _irsend.send(_protocol, _swingv, kLgBits, repeat); - updateSwingVPrev(); - } // 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; + case lg_ac_remote_model_t::AKB73757604: + // Check if we need to send any vane specific swingv's. + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) // For all vanes + if (_vaneswingv[i] != _vaneswingv_prev[i]) // Only send if we must. + _irsend.send(_protocol, calcVaneSwingV(i, _vaneswingv[i]), kLgBits, + repeat); + // and if we need to send a swingh message. + if (_swingh != _swingh_prev) + _irsend.send(_protocol, _swingh ? kLgAcSwingHAuto : kLgAcSwingHOff, + kLgBits, repeat); + break; default: break; } + updateSwingPrev(); // Swing changes will have been sent, so make them prev. } else { // Always send the special Off command if the power is set to off. // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1008#issuecomment-570763580 @@ -269,7 +283,7 @@ bool IRLgAc::_isNormal(void) const { case kLgAcLightToggle: return false; } - if (isSwingV()) return false; + if (isSwing()) return false; return true; } @@ -279,6 +293,7 @@ void IRLgAc::setModel(const lg_ac_remote_model_t model) { switch (model) { case lg_ac_remote_model_t::AKB75215403: case lg_ac_remote_model_t::AKB74955603: + case lg_ac_remote_model_t::AKB73757604: _protocol = decode_type_t::LG2; break; case lg_ac_remote_model_t::GE6711AR2853M: @@ -300,10 +315,18 @@ lg_ac_remote_model_t IRLgAc::getModel(void) const { /// @return true, if it is AKB74955603 message. Otherwise, false. /// @note Internal use only. bool IRLgAc::_isAKB74955603(void) const { - return ((_.raw & kLgAcAKB74955603DetectionMask) || isSwingV() || + return (((_.raw & kLgAcAKB74955603DetectionMask) && _isNormal()) || + isSwingV() || isLightToggle()); } +/// Check if the stored code must belong to a AKB73757604 model. +/// @return true, if it is AKB73757604 message. Otherwise, false. +/// @note Internal use only. +bool IRLgAc::_isAKB73757604(void) const { + return isSwingH() || isVaneSwingV(); +} + /// Get a copy of the internal state/code for this protocol. /// @return The code for this protocol based on the current internal state. uint32_t IRLgAc::getRaw(void) { @@ -329,10 +352,22 @@ void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) { break; } // Look for model specific settings/features to improve model detection. - if (_isAKB74955603()) setModel(lg_ac_remote_model_t::AKB74955603); + if (_isAKB74955603()) { + setModel(lg_ac_remote_model_t::AKB74955603); + if (isSwingV()) _swingv = new_code; + } + if (_isAKB73757604()) { + setModel(lg_ac_remote_model_t::AKB73757604); + if (isVaneSwingV()) { + // Extract just the vane nr and position part of the message. + const uint32_t vanecode = getVaneCode(_.raw); + _vaneswingv[vanecode / kLgAcVaneSwingVSize] = vanecode & 0xF; + } else if (isSwingH()) { + _swingh = (_.raw == kLgAcSwingHAuto); + } + } _temp = 15; // Ensure there is a "sane" previous temp. _temp = getTemp(); - if (isSwingV()) _swingv = new_code; } /// Calculate the checksum for a given state. @@ -477,10 +512,39 @@ 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 { +bool IRLgAc::isSwing(void) const { return (_.raw >> 12) == kLgAcSwingSignature; } +/// Check if the stored code is a non-vane SwingV message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingV(void) const { + const uint32_t code = _.raw >> 4; + return code >= (kLgAcSwingVLowest >> 4) && code < (kLgAcSwingHAuto >> 4); +} + +/// Check if the stored code is a SwingH message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isSwingH(void) const { + return (_.raw >> 5) == kLgAcSwingHSignature; +} + +/// Get the Horizontal Swing position setting of the A/C. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::getSwingH(void) const { return _swingh; } + +/// Set the Horizontal Swing mode of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRLgAc::setSwingH(const bool on) { _swingh = on; } + +/// Check if the stored code is a vane specific SwingV message. +/// @return true, if it is. Otherwise, false. +bool IRLgAc::isVaneSwingV(void) const { + return (_.raw > kLgAcVaneSwingVBase && + _.raw < (kLgAcVaneSwingVBase + + ((kLgAcSwingVMaxVanes * kLgAcVaneSwingVSize) << 4))); +} + /// 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) { @@ -496,13 +560,49 @@ void IRLgAc::setSwingV(const uint32_t position) { } } -// Copy the previous swingv setting the current one. -void IRLgAc::updateSwingVPrev(void) { _swingv_prev = _swingv; } +// Copy the previous swing settings from the current ones. +void IRLgAc::updateSwingPrev(void) { + _swingv_prev = _swingv; + for (uint8_t i = 0; i < kLgAcSwingVMaxVanes; i++) + _vaneswingv_prev[i] = _vaneswingv[i]; +} /// Get the Vertical Swing position setting of the A/C. /// @return The native position/mode. uint32_t IRLgAc::getSwingV(void) const { return _swingv; } +/// Set the per Vane Vertical Swing mode of the A/C. +/// @param[in] vane The nr. of the vane to control. +/// @param[in] position The position/mode to set the vanes to. +void IRLgAc::setVaneSwingV(const uint8_t vane, const uint8_t position) { + if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr. + if (position && position <= kLgAcVaneSwingVLowest) // Valid position + _vaneswingv[vane] = position; +} + +/// Get the Vertical Swing position for the given vane of the A/C. +/// @return The native position/mode. +uint8_t IRLgAc::getVaneSwingV(const uint8_t vane) const { + return (vane < kLgAcSwingVMaxVanes) ? _vaneswingv[vane] : 0; +} + +/// Get the vane code of a Vane Vertical Swing message. +/// @param[in] raw A raw number representing a native LG message. +/// @return A number containing just the vane nr, and the position. +uint8_t IRLgAc::getVaneCode(const uint32_t raw) { + return (raw - kLgAcVaneSwingVBase) >> 4; +} + +/// Calculate the Vane specific Vertical Swing code for the A/C. +/// @return The native raw code. +uint32_t IRLgAc::calcVaneSwingV(const uint8_t vane, const uint8_t position) { + uint32_t result = kLgAcVaneSwingVBase; + if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr. + if (position && position <= kLgAcVaneSwingVLowest) // Valid position + result += ((vane * kLgAcVaneSwingVSize + position) << 4); + return result | calcChecksum(result); +} + /// 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. @@ -596,6 +696,33 @@ stdAc::swingv_t IRLgAc::toCommonSwingV(const uint32_t code) { } } +/// Convert a native Vane specific 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::toCommonVaneSwingV(const uint8_t pos) { + switch (pos) { + case kLgAcVaneSwingVHigh: return stdAc::swingv_t::kHigh; + case kLgAcVaneSwingVUpperMiddle: + case kLgAcVaneSwingVMiddle: return stdAc::swingv_t::kMiddle; + case kLgAcVaneSwingVLow: return stdAc::swingv_t::kLow; + case kLgAcVaneSwingVLowest: return stdAc::swingv_t::kLowest; + default: return stdAc::swingv_t::kHighest; + } +} + +/// 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. +uint8_t IRLgAc::convertVaneSwingV(const stdAc::swingv_t swingv) { + switch (swingv) { + case stdAc::swingv_t::kHigh: return kLgAcVaneSwingVHigh; + case stdAc::swingv_t::kMiddle: return kLgAcVaneSwingVMiddle; + case stdAc::swingv_t::kLow: return kLgAcVaneSwingVLow; + case stdAc::swingv_t::kLowest: return kLgAcVaneSwingVLowest; + default: return kLgAcVaneSwingVHighest; + } +} + /// 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. @@ -620,8 +747,10 @@ stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const { result.fanspeed = toCommonFanSpeed(_.Fan); result.light = isLightToggle() ? !result.light : _light; if (isSwingV()) result.swingv = toCommonSwingV(getSwingV()); + if (isVaneSwingV()) + result.swingv = toCommonVaneSwingV(getVaneCode(_.raw) & 0xF); + result.swingh = isSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; // Not supported. - result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.turbo = false; result.filter = false; @@ -639,7 +768,7 @@ String IRLgAc::toString(void) const { String result = ""; result.reserve(80); // Reserve some heap for the string to reduce fragging. result += addModelToString(_protocol, getModel(), false); - if (_isNormal()) { + if (_isNormal()) { // A "Normal" generic settings message. result += addBoolToString(getPower(), kPowerStr); if (getPower()) { // Only display the rest if is in power on state. result += addModeToString(_.Mode, kLgAcAuto, kLgAcCool, @@ -650,10 +779,14 @@ String IRLgAc::toString(void) const { kLgAcFanAuto, kLgAcFanLowest, kLgAcFanMedium, kLgAcFanMax); } - } else { - if (isOffCommand()) result += addBoolToString(false, kPowerStr); - if (isLightToggle()) result += addBoolToString(true, kLightToggleStr); - if (isSwingV()) + } else { // It must be a special single purpose code. + if (isOffCommand()) { + result += addBoolToString(false, kPowerStr); + } else if (isLightToggle()) { + result += addBoolToString(true, kLightToggleStr); + } else if (isSwingH()) { + result += addBoolToString(_swingh, kSwingHStr); + } else if (isSwingV()) { result += addSwingVToString((uint8_t)(_swingv >> 4), 0, // No Auto, See "swing". Unused kLgAcSwingVHighest_Short, @@ -666,6 +799,21 @@ String IRLgAc::toString(void) const { kLgAcSwingVOff_Short, kLgAcSwingVSwing_Short, 0, 0); + } else if (isVaneSwingV()) { + const uint8_t vane = getVaneCode(_.raw) / kLgAcVaneSwingVSize; + result += addIntToString(vane, kVaneStr); + result += addSwingVToString(_vaneswingv[vane], + 0, // No Auto, See "swing". Unused + kLgAcVaneSwingVHighest, + kLgAcVaneSwingVHigh, + kLgAcVaneSwingVUpperMiddle, + kLgAcVaneSwingVMiddle, + 0, // Unused + kLgAcVaneSwingVLow, + kLgAcVaneSwingVLowest, + // Rest unused + 0, 0, 0, 0); + } } return result; } diff --git a/src/ir_LG.h b/src/ir_LG.h index 98a09d78f..55f47cb7e 100644 --- a/src/ir_LG.h +++ b/src/ir_LG.h @@ -11,8 +11,10 @@ // Brand: LG, Model: S4-W12JA3AA A/C (LG2) // Brand: LG, Model: AKB75215403 remote (LG2) // Brand: LG, Model: AKB74955603 remote (LG2 - AKB74955603) -// Brand: LG, Model: A4UW30GFA2 A/C (LG2 - AKB74955603) +// Brand: LG, Model: A4UW30GFA2 A/C (LG2 - AKB74955603 & AKB73757604) // Brand: LG, Model: AMNW09GSJA0 A/C (LG2 - AKB74955603) +// Brand: LG, Model: AMNW24GTPA1 A/C (LG2 - AKB73757604) +// Brand: LG, Model: AKB73757604 remote (LG2 - AKB73757604) // Brand: General Electric, Model: AG1BH09AW101 Split A/C (LG) // Brand: General Electric, Model: 6711AR2853M A/C Remote (LG) @@ -89,6 +91,20 @@ const uint8_t kLgAcSwingVSwing_Short = 0x14; const uint8_t kLgAcSwingVAuto_Short = kLgAcSwingVSwing_Short; const uint8_t kLgAcSwingVOff_Short = 0x15; +// AKB73757604 +const uint32_t kLgAcSwingHAuto = 0x881316B; +const uint32_t kLgAcSwingHOff = 0x881317C; +const uint32_t kLgAcSwingHSignature = kLgAcSwingHOff >> 5; +const uint32_t kLgAcVaneSwingVBase = 0x8813200; +const uint8_t kLgAcVaneSwingVHighest = 1; ///< 0b001 +const uint8_t kLgAcVaneSwingVHigh = 2; ///< 0b010 +const uint8_t kLgAcVaneSwingVUpperMiddle = 3; ///< 0b011 +const uint8_t kLgAcVaneSwingVMiddle = 4; ///< 0b100 +const uint8_t kLgAcVaneSwingVLow = 5; ///< 0b101 +const uint8_t kLgAcVaneSwingVLowest = 6; ///< 0b110 +const uint8_t kLgAcVaneSwingVSize = 8; +const uint8_t kLgAcSwingVMaxVanes = 4; ///< Max Nr. of Vanes + // Classes /// Class for handling detailed LG A/C messages. class IRLgAc { @@ -122,10 +138,19 @@ class IRLgAc { void setLight(const bool on); bool getLight(void) const; bool isLightToggle(void) const; + bool isSwing(void) const; + void setSwingH(const bool on); + bool getSwingH(void) const; bool isSwingV(void) const; + bool isVaneSwingV(void) const; void setSwingV(const uint32_t position); uint32_t getSwingV(void) const; - void updateSwingVPrev(void); + void setVaneSwingV(const uint8_t vane, const uint8_t position); + uint8_t getVaneSwingV(const uint8_t vane) const; + static uint32_t calcVaneSwingV(const uint8_t vane, const uint8_t position); + static uint8_t getVaneCode(const uint32_t raw); + bool isSwingH(void) const; + void updateSwingPrev(void); uint32_t getRaw(void); void setRaw(const uint32_t new_code, const decode_type_t protocol = decode_type_t::UNKNOWN); @@ -133,8 +158,10 @@ class IRLgAc { 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 stdAc::swingv_t toCommonVaneSwingV(const uint8_t pos); static uint8_t convertFan(const stdAc::fanspeed_t speed); static uint32_t convertSwingV(const stdAc::swingv_t swingv); + static uint8_t convertVaneSwingV(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); @@ -153,11 +180,16 @@ class IRLgAc { bool _light; uint32_t _swingv; uint32_t _swingv_prev; + uint8_t _vaneswingv[kLgAcSwingVMaxVanes]; + uint8_t _vaneswingv_prev[kLgAcSwingVMaxVanes]; + bool _swingh; + bool _swingh_prev; decode_type_t _protocol; ///< Protocol version lg_ac_remote_model_t _model; ///< Model type void checksum(void); void _setTemp(const uint8_t value); bool _isAKB74955603(void) const; + bool _isAKB73757604(void) const; bool _isNormal(void) const; }; diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 8c6096289..3a9ba1822 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -279,6 +279,9 @@ #ifndef D_STR_ID #define D_STR_ID "Id" #endif // D_STR_ID +#ifndef D_STR_VANE +#define D_STR_VANE "Vane" +#endif // D_STR_VANE #ifndef D_STR_AUTO #define D_STR_AUTO "Auto" diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 9437fe656..cc6de5a2f 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1018,6 +1018,7 @@ TEST(TestIRac, LG) { stdAc::fanspeed_t::kMedium, // Fan speed stdAc::swingv_t::kLow, // Vertical swing stdAc::swingv_t::kOff, // Vertical swing (previous) + stdAc::swingh_t::kOff, // Horizontal swing true); // Light ASSERT_EQ(expected, ac.toString()); @@ -1049,6 +1050,7 @@ TEST(TestIRac, LG2) { stdAc::fanspeed_t::kLow, // Fan speed stdAc::swingv_t::kLow, // Vertical swing stdAc::swingv_t::kOff, // Vertical swing (previous) + stdAc::swingh_t::kOff, // Horizontal swing false); // Light ASSERT_EQ(expected, ac.toString()); @@ -1093,6 +1095,7 @@ TEST(TestIRac, Issue1513) { stdAc::fanspeed_t::kMin, // Fan speed stdAc::swingv_t::kAuto, // Vertical swing stdAc::swingv_t::kHighest, // Vertical swing (previous) + stdAc::swingh_t::kOff, // Horizontal swing true); // Light ac._irsend.makeDecodeResult(); // All sent, we assume the above works. Just need to switch to swing off now. @@ -1111,6 +1114,7 @@ TEST(TestIRac, Issue1513) { stdAc::fanspeed_t::kMin, // Fan speed stdAc::swingv_t::kOff, // Vertical swing stdAc::swingv_t::kAuto, // Vertical swing (previous) + stdAc::swingh_t::kOff, // Horizontal swing true); // Light ac._irsend.makeDecodeResult(); // There should only be two messages. @@ -1132,6 +1136,62 @@ TEST(TestIRac, Issue1513) { IRAcUtils::resultAcToString(&ac._irsend.capture)); } +TEST(TestIRac, LG2_AKB73757604) { + IRLgAc ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + char expected[] = + "Model: 2 (AKB75215403), " + "Power: On, Mode: 1 (Dry), Temp: 27C, Fan: 1 (Low)"; + + ac.begin(); + irac.lg(&ac, + lg_ac_remote_model_t::AKB73757604, // Model + true, // Power + stdAc::opmode_t::kDry, // Mode + 27, // Degrees C + stdAc::fanspeed_t::kLow, // Fan speed + stdAc::swingv_t::kLow, // Vertical swing + stdAc::swingv_t::kOff, // Vertical swing (previous) + stdAc::swingh_t::kAuto, // Horizontal swing + true); // Light + + ac._irsend.makeDecodeResult(); + ASSERT_EQ(361, ac._irsend.capture.rawlen); // We expect six 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 - Vane 0 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(0x881325B, ac._irsend.capture.value); + // Message #3 - Vane 1 SwingV Low + 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(0x88132D3, ac._irsend.capture.value); + // Message #4 - Vane 2 SwingV Low + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 181)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(0x881335C, ac._irsend.capture.value); + // Message #5 - Vane 3 SwingV Low + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 241)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(0x88133D4, ac._irsend.capture.value); + // Message #6 - Horizontal swing + EXPECT_TRUE(capture.decodeLG(&ac._irsend.capture, 301)); + ASSERT_EQ(LG2, ac._irsend.capture.decode_type); + ASSERT_EQ(kLgBits, ac._irsend.capture.bits); + ASSERT_EQ(kLgAcSwingHAuto, 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 fea1280fd..6bacb044d 100644 --- a/test/ir_LG_test.cpp +++ b/test/ir_LG_test.cpp @@ -689,6 +689,10 @@ TEST(TestUtils, Housekeeping) { IRac::strToModel(irutils::modelToStr( decode_type_t::LG2, lg_ac_remote_model_t::AKB74955603).c_str())); + ASSERT_EQ(lg_ac_remote_model_t::AKB73757604, + IRac::strToModel(irutils::modelToStr( + decode_type_t::LG2, + lg_ac_remote_model_t::AKB73757604).c_str())); } TEST(TestIRLgAcClass, KnownExamples) { @@ -954,6 +958,37 @@ TEST(TestIRLgAcClass, DetectAKB74955603) { ac.stateReset(); ac.setRaw(0x881306A); EXPECT_EQ(lg_ac_remote_model_t::AKB74955603, ac.getModel()); + + ac.stateReset(); + ac.setRaw(kLgAcSwingHOff); + EXPECT_NE(lg_ac_remote_model_t::AKB74955603, ac.getModel()); + + ac.stateReset(); + ac.setRaw(0x8813228); + EXPECT_NE(lg_ac_remote_model_t::AKB74955603, ac.getModel()); +} + +TEST(TestIRLgAcClass, DetectAKB73757604) { + IRLgAc ac(kGpioUnused); + IRrecv capture(kGpioUnused); + + ac.stateReset(); + ASSERT_NE(lg_ac_remote_model_t::AKB73757604, ac.getModel()); + ac.setRaw(0x880A3A7); + EXPECT_NE(lg_ac_remote_model_t::AKB73757604, ac.getModel()); + + // https://docs.google.com/spreadsheets/d/17C_Ay7OjsYNSAxxj8uXbh0Vi2jrqyrncwzIyUOGSuNo/edit?usp=sharing + ac.stateReset(); + ac.setRaw(kLgAcSwingHOff); + EXPECT_EQ(lg_ac_remote_model_t::AKB73757604, ac.getModel()); + + ac.setRaw(0x8813228); + EXPECT_EQ(lg_ac_remote_model_t::AKB73757604, ac.getModel()); + + ac.setRaw(0x881333A); + EXPECT_EQ(lg_ac_remote_model_t::AKB73757604, ac.getModel()); + ASSERT_EQ("Model: 4 (AKB73757604), Vane: 2, Swing(V): 3 (Upper Middle)", + ac.toString()); } TEST(TestIRLgAcClass, Light) { From 0705d9f29865501e1c3d6311feee4feb32348ac9 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 3 Aug 2021 16:35:15 +1000 Subject: [PATCH 2/5] Additional unit test data. --- test/ir_LG_test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/ir_LG_test.cpp b/test/ir_LG_test.cpp index 6bacb044d..84c3c1098 100644 --- a/test/ir_LG_test.cpp +++ b/test/ir_LG_test.cpp @@ -816,6 +816,13 @@ TEST(TestIRLgAcClass, KnownExamples) { "Model: 1 (GE6711AR2853M), " "Power: On, Mode: 0 (Cool), Temp: 15C, Fan: 5 (Auto)", ac.toString()); + + // https://docs.google.com/spreadsheets/d/17C_Ay7OjsYNSAxxj8uXbh0Vi2jrqyrncwzIyUOGSuNo/edit#gid=0&range=A56:E56 + ac.setRaw(0x881334B); + ASSERT_TRUE(ac.isValidLgAc()); + EXPECT_EQ( + "Model: 4 (AKB73757604), Vane: 2, Swing(V): 4 (Middle)", + ac.toString()); } // Verify decoding of LG2 message. From ba357c458be0940b59b608434b5e0d54c2426b94 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 3 Aug 2021 17:46:00 +1000 Subject: [PATCH 3/5] Doxygen fixes. --- src/IRac.cpp | 1 + src/ir_LG.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index f5bfa900c..307641368 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1335,6 +1335,7 @@ void IRac::kelvinator(IRKelvinatorAC *ac, /// @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] swingh The horizontal 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, diff --git a/src/ir_LG.cpp b/src/ir_LG.cpp index 5c73f9ca6..8f4fd6e50 100644 --- a/src/ir_LG.cpp +++ b/src/ir_LG.cpp @@ -697,7 +697,7 @@ stdAc::swingv_t IRLgAc::toCommonSwingV(const uint32_t code) { } /// Convert a native Vane specific Vertical Swing into its stdAc equivalent. -/// @param[in] code The native code to be converted. +/// @param[in] pos The native position to be converted. /// @return The stdAc equivalent of the native setting. stdAc::swingv_t IRLgAc::toCommonVaneSwingV(const uint8_t pos) { switch (pos) { From 528bca2c14f7de0cb45473001deacae0055d7a24 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 3 Aug 2021 20:19:11 +1000 Subject: [PATCH 4/5] Respond to codereview feedback. * Use more constants and comments * Use a macro() for some stuff to make it clearer. --- src/ir_LG.cpp | 51 ++++++++++++++++++++++++++++++++------------------- src/ir_LG.h | 6 +++--- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/ir_LG.cpp b/src/ir_LG.cpp index 8f4fd6e50..c2e115801 100644 --- a/src/ir_LG.cpp +++ b/src/ir_LG.cpp @@ -47,6 +47,16 @@ const uint16_t kLg2HdrSpace = 9900; ///< uSeconds. const uint16_t kLg2BitMark = 480; ///< uSeconds. const uint32_t kLgAcAKB74955603DetectionMask = 0x0000080; +const uint8_t kLgAcChecksumSize = 4; ///< Size in bits. +// Signature has the checksum removed, and another bit to match both Auto & Off. +const uint8_t kLgAcSwingHOffsetSize = kLgAcChecksumSize + 1; +const uint32_t kLgAcSwingHSignature = kLgAcSwingHOff >> kLgAcSwingHOffsetSize; +const uint32_t kLgAcVaneSwingVBase = 0x8813200; + +#ifdef VANESWINGVPOS +#undef VANESWINGVPOS +#endif +#define VANESWINGVPOS(code) (code & 0xF) #if SEND_LG /// Send an LG formatted message. (LG) @@ -119,7 +129,8 @@ void IRsend::sendLG2(uint64_t data, uint16_t nbits, uint16_t repeat) { /// @return A raw 28-bit LG message code suitable for sendLG() etc. /// @note Sequence of bits = address + command + checksum. uint32_t IRsend::encodeLG(uint16_t address, uint16_t command) { - return ((address << 20) | (command << 4) | irutils::sumNibbles(command, 4)); + return ((address << 20) | (command << kLgAcChecksumSize) | + irutils::sumNibbles(command, 4)); } #endif // SEND_LG @@ -192,9 +203,10 @@ bool IRrecv::decodeLG(decode_results *results, uint16_t offset, kBitmark, kLgMinGap, true, kUseDefTol)) return false; } - // Compliance - uint16_t command = (data >> 4); // The 16 bits before the checksum. + // The 16 bits before the checksum. + uint16_t command = (data >> kLgAcChecksumSize); + // Compliance if (strict && (data & 0xF) != irutils::sumNibbles(command, 4)) return false; // The last 4 bits sent are the expected checksum. // Success @@ -315,9 +327,8 @@ lg_ac_remote_model_t IRLgAc::getModel(void) const { /// @return true, if it is AKB74955603 message. Otherwise, false. /// @note Internal use only. bool IRLgAc::_isAKB74955603(void) const { - return (((_.raw & kLgAcAKB74955603DetectionMask) && _isNormal()) || - isSwingV() || - isLightToggle()); + return ((_.raw & kLgAcAKB74955603DetectionMask) && _isNormal()) || + isSwingV() || isLightToggle(); } /// Check if the stored code must belong to a AKB73757604 model. @@ -361,7 +372,7 @@ void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) { if (isVaneSwingV()) { // Extract just the vane nr and position part of the message. const uint32_t vanecode = getVaneCode(_.raw); - _vaneswingv[vanecode / kLgAcVaneSwingVSize] = vanecode & 0xF; + _vaneswingv[vanecode / kLgAcVaneSwingVSize] = VANESWINGVPOS(vanecode); } else if (isSwingH()) { _swingh = (_.raw == kLgAcSwingHAuto); } @@ -374,7 +385,7 @@ void IRLgAc::setRaw(const uint32_t new_code, const decode_type_t protocol) { /// @param[in] state The value to calc the checksum of. /// @return The calculated checksum value. uint8_t IRLgAc::calcChecksum(const uint32_t state) { - return irutils::sumNibbles(state >> 4, 4); + return irutils::sumNibbles(state >> kLgAcChecksumSize, 4); } /// Verify the checksum is valid for a given state. @@ -519,14 +530,15 @@ bool IRLgAc::isSwing(void) const { /// Check if the stored code is a non-vane SwingV message. /// @return true, if it is. Otherwise, false. bool IRLgAc::isSwingV(void) const { - const uint32_t code = _.raw >> 4; - return code >= (kLgAcSwingVLowest >> 4) && code < (kLgAcSwingHAuto >> 4); + const uint32_t code = _.raw >> kLgAcChecksumSize; + return code >= (kLgAcSwingVLowest >> kLgAcChecksumSize) && + code < (kLgAcSwingHAuto >> kLgAcChecksumSize); } /// Check if the stored code is a SwingH message. /// @return true, if it is. Otherwise, false. bool IRLgAc::isSwingH(void) const { - return (_.raw >> 5) == kLgAcSwingHSignature; + return (_.raw >> kLgAcSwingHOffsetSize) == kLgAcSwingHSignature; } /// Get the Horizontal Swing position setting of the A/C. @@ -540,9 +552,10 @@ void IRLgAc::setSwingH(const bool on) { _swingh = on; } /// Check if the stored code is a vane specific SwingV message. /// @return true, if it is. Otherwise, false. bool IRLgAc::isVaneSwingV(void) const { - return (_.raw > kLgAcVaneSwingVBase && - _.raw < (kLgAcVaneSwingVBase + - ((kLgAcSwingVMaxVanes * kLgAcVaneSwingVSize) << 4))); + return _.raw > kLgAcVaneSwingVBase && + _.raw < (kLgAcVaneSwingVBase + + ((kLgAcSwingVMaxVanes * + kLgAcVaneSwingVSize) << kLgAcChecksumSize)); } /// Set the Vertical Swing mode of the A/C. @@ -552,7 +565,7 @@ void IRLgAc::setSwingV(const uint32_t position) { 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 = (kLgAcSwingSignature << 8 | position) << kLgAcChecksumSize; _swingv |= calcChecksum(_swingv); } else { _swingv = position; @@ -590,7 +603,7 @@ uint8_t IRLgAc::getVaneSwingV(const uint8_t vane) const { /// @param[in] raw A raw number representing a native LG message. /// @return A number containing just the vane nr, and the position. uint8_t IRLgAc::getVaneCode(const uint32_t raw) { - return (raw - kLgAcVaneSwingVBase) >> 4; + return (raw - kLgAcVaneSwingVBase) >> kLgAcChecksumSize; } /// Calculate the Vane specific Vertical Swing code for the A/C. @@ -599,7 +612,7 @@ uint32_t IRLgAc::calcVaneSwingV(const uint8_t vane, const uint8_t position) { uint32_t result = kLgAcVaneSwingVBase; if (vane < kLgAcSwingVMaxVanes) // It's a valid vane nr. if (position && position <= kLgAcVaneSwingVLowest) // Valid position - result += ((vane * kLgAcVaneSwingVSize + position) << 4); + result += ((vane * kLgAcVaneSwingVSize + position) << kLgAcChecksumSize); return result | calcChecksum(result); } @@ -748,7 +761,7 @@ stdAc::state_t IRLgAc::toCommon(const stdAc::state_t *prev) const { result.light = isLightToggle() ? !result.light : _light; if (isSwingV()) result.swingv = toCommonSwingV(getSwingV()); if (isVaneSwingV()) - result.swingv = toCommonVaneSwingV(getVaneCode(_.raw) & 0xF); + result.swingv = toCommonVaneSwingV(VANESWINGVPOS(getVaneCode(_.raw))); result.swingh = isSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; // Not supported. result.quiet = false; @@ -787,7 +800,7 @@ String IRLgAc::toString(void) const { } else if (isSwingH()) { result += addBoolToString(_swingh, kSwingHStr); } else if (isSwingV()) { - result += addSwingVToString((uint8_t)(_swingv >> 4), + result += addSwingVToString((uint8_t)(_swingv >> kLgAcChecksumSize), 0, // No Auto, See "swing". Unused kLgAcSwingVHighest_Short, kLgAcSwingVHigh_Short, diff --git a/src/ir_LG.h b/src/ir_LG.h index 55f47cb7e..6010282ca 100644 --- a/src/ir_LG.h +++ b/src/ir_LG.h @@ -91,11 +91,11 @@ const uint8_t kLgAcSwingVSwing_Short = 0x14; const uint8_t kLgAcSwingVAuto_Short = kLgAcSwingVSwing_Short; const uint8_t kLgAcSwingVOff_Short = 0x15; -// AKB73757604 +// AKB73757604 Constants +// SwingH const uint32_t kLgAcSwingHAuto = 0x881316B; const uint32_t kLgAcSwingHOff = 0x881317C; -const uint32_t kLgAcSwingHSignature = kLgAcSwingHOff >> 5; -const uint32_t kLgAcVaneSwingVBase = 0x8813200; +// SwingV const uint8_t kLgAcVaneSwingVHighest = 1; ///< 0b001 const uint8_t kLgAcVaneSwingVHigh = 2; ///< 0b010 const uint8_t kLgAcVaneSwingVUpperMiddle = 3; ///< 0b011 From 46573ca2fe3d683c85c19a05037b507e25eee623 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Wed, 4 Aug 2021 10:36:35 +1000 Subject: [PATCH 5/5] Fix problem with vane position decoding. * Fix a dumb math fail. * Add unit test to make sure it stays fixed. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1531#issuecomment-892070033 --- src/ir_LG.cpp | 2 +- test/ir_LG_test.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ir_LG.cpp b/src/ir_LG.cpp index c2e115801..d817fbdb7 100644 --- a/src/ir_LG.cpp +++ b/src/ir_LG.cpp @@ -56,7 +56,7 @@ const uint32_t kLgAcVaneSwingVBase = 0x8813200; #ifdef VANESWINGVPOS #undef VANESWINGVPOS #endif -#define VANESWINGVPOS(code) (code & 0xF) +#define VANESWINGVPOS(code) (code % kLgAcVaneSwingVSize) #if SEND_LG /// Send an LG formatted message. (LG) diff --git a/test/ir_LG_test.cpp b/test/ir_LG_test.cpp index 84c3c1098..808f7b83a 100644 --- a/test/ir_LG_test.cpp +++ b/test/ir_LG_test.cpp @@ -823,6 +823,13 @@ TEST(TestIRLgAcClass, KnownExamples) { EXPECT_EQ( "Model: 4 (AKB73757604), Vane: 2, Swing(V): 4 (Middle)", ac.toString()); + + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1531#issuecomment-892070033 + ac.setRaw(0x88133B2); + ASSERT_TRUE(ac.isValidLgAc()); + EXPECT_EQ( + "Model: 4 (AKB73757604), Vane: 3, Swing(V): 3 (Upper Middle)", + ac.toString()); } // Verify decoding of LG2 message.