Skip to content

Commit

Permalink
[LG] Add support for AKB73757604 model
Browse files Browse the repository at this point in the history
* Support for Vertical Swing on multiple vanes.
* Support for Horizontal Swing.
* Model detection.
* Unit tests added & updated.

Fixes #1531
  • Loading branch information
crankyoldgit committed Aug 3, 2021
1 parent 89ead22 commit 8bd0555
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 26 deletions.
14 changes: 10 additions & 4 deletions src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1340,17 +1340,20 @@ 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);
ac->setMode(ac->convertMode(mode));
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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/IRac.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/IRsend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};


Expand Down
1 change: 1 addition & 0 deletions src/IRtext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/IRtext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
182 changes: 165 additions & 17 deletions src/ir_LG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ using irutils::addModelToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addSwingVToString;
using irutils::addIntToString;


// Constants
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -269,7 +283,7 @@ bool IRLgAc::_isNormal(void) const {
case kLgAcLightToggle:
return false;
}
if (isSwingV()) return false;
if (isSwing()) return false;
return true;
}

Expand All @@ -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:
Expand All @@ -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) {
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit 8bd0555

Please sign in to comment.