Skip to content

Commit

Permalink
Support for SwingV & Light
Browse files Browse the repository at this point in the history
* Add support for SwingV settings. (A large mess.)
* Add missing `IRac` support for light.
* Update/Add unit tests.

Fixes #1513
  • Loading branch information
crankyoldgit committed Jul 11, 2021
1 parent 302d215 commit daf35d4
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 18 deletions.
11 changes: 7 additions & 4 deletions src/IRac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/IRac.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
113 changes: 103 additions & 10 deletions src/ir_LG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ using irutils::addModeToString;
using irutils::addModelToString;
using irutils::addFanToString;
using irutils::addTempToString;
using irutils::addSwingVToString;


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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
31 changes: 29 additions & 2 deletions src/ir_LG.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
46 changes: 45 additions & 1 deletion test/IRac_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1013,18 +1013,62 @@ 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();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
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);
Expand Down
Loading

0 comments on commit daf35d4

Please sign in to comment.