Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ELECTRA_AC] Add support for "IFeel" & Sensor settings. #1645

Merged
merged 2 commits into from
Oct 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 67 additions & 14 deletions src/ir_Electra.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018, 2019 David Conran
// Copyright 2018-2021 David Conran
/// @file
/// @brief Support for Electra A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp
Expand Down Expand Up @@ -310,6 +310,52 @@ bool IRElectraAc::getTurbo(void) const {
return _.Turbo;
}

/// Get the IFeel mode of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getIFeel(void) const { return _.IFeel; }

/// Set the IFeel mode of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setIFeel(const bool on) {
_.IFeel = on;
if (_.IFeel)
// Make sure there is a reasonable value in _.SensorTemp
setSensorTemp(getSensorTemp());
else
// Clear any previous stored temp..
_.SensorTemp = kElectraAcSensorMinTemp;
}

/// Get the silent Sensor Update setting of the message.
/// i.e. Is this _just_ a sensor temp update message from the remote?
/// @note The A/C just takes the sensor temp value from the message and
/// will not follow any of the other settings in the message.
/// @return true, the setting is on. false, the setting is off.
bool IRElectraAc::getSensorUpdate(void) const { return _.SensorUpdate; }

/// Set the silent Sensor Update setting of the message.
/// i.e. Is this _just_ a sensor temp update message from the remote?
/// @note The A/C will just take the sensor temp value from the message and
/// will not follow any of the other settings in the message. If set, the A/C
/// unit will also not beep in response to the message.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRElectraAc::setSensorUpdate(const bool on) { _.SensorUpdate = on; }

/// Set the Sensor temperature for the IFeel mode.
/// @param[in] temp The temperature in degrees celsius.
void IRElectraAc::setSensorTemp(const uint8_t temp) {
_.SensorTemp = std::min(kElectraAcSensorMaxTemp,
std::max(kElectraAcSensorMinTemp, temp)) +
kElectraAcSensorTempDelta;
}

/// Get the current sensor temperature setting for the IFeel mode.
/// @return The current setting for temp. in degrees celsius.
uint8_t IRElectraAc::getSensorTemp(void) const {
return std::max(kElectraAcSensorTempDelta, _.SensorTemp) -
kElectraAcSensorTempDelta;
}

/// Convert the current internal state into its stdAc::state_t equivalent.
/// @return The stdAc equivalent of the native settings.
stdAc::state_t IRElectraAc::toCommon(void) const {
Expand Down Expand Up @@ -342,19 +388,26 @@ stdAc::state_t IRElectraAc::toCommon(void) const {
/// @return A human readable string.
String IRElectraAc::toString(void) const {
String result = "";
result.reserve(130); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kElectraAcAuto, kElectraAcCool,
kElectraAcHeat, kElectraAcDry, kElectraAcFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kElectraAcFanHigh, kElectraAcFanLow,
kElectraAcFanAuto, kElectraAcFanAuto,
kElectraAcFanMed);
result += addBoolToString(getSwingV(), kSwingVStr);
result += addBoolToString(getSwingH(), kSwingHStr);
result += addToggleToString(getLightToggle(), kLightStr);
result += addBoolToString(_.Clean, kCleanStr);
result += addBoolToString(_.Turbo, kTurboStr);
result.reserve(160); // Reserve some heap for the string to reduce fragging.
if (!_.SensorUpdate) {
result += addBoolToString(_.Power, kPowerStr, false);
result += addModeToString(_.Mode, kElectraAcAuto, kElectraAcCool,
kElectraAcHeat, kElectraAcDry, kElectraAcFan);
result += addTempToString(getTemp());
result += addFanToString(_.Fan, kElectraAcFanHigh, kElectraAcFanLow,
kElectraAcFanAuto, kElectraAcFanAuto,
kElectraAcFanMed);
result += addBoolToString(getSwingV(), kSwingVStr);
result += addBoolToString(getSwingH(), kSwingHStr);
result += addToggleToString(getLightToggle(), kLightStr);
result += addBoolToString(_.Clean, kCleanStr);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.IFeel, kIFeelStr);
}
if (_.IFeel || _.SensorUpdate) {
result += addIntToString(getSensorTemp(), kSensorTempStr, !_.SensorUpdate);
result += 'C';
}
return result;
}

Expand Down
27 changes: 23 additions & 4 deletions src/ir_Electra.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 David Conran
// Copyright 2019-2021 David Conran
/// @file
/// @brief Support for Electra A/C protocols.
/// @see https://github.com/ToniA/arduino-heatpumpir/blob/master/AUXHeatpumpIR.cpp
Expand All @@ -9,6 +9,10 @@
// Brand: Electra, Model: Classic INV 17 / AXW12DCS A/C
// Brand: Electra, Model: YKR-M/003E remote
// Brand: Frigidaire, Model: FGPC102AB1 A/C
// Brand: Subtropic, Model: SUB-07HN1_18Y A/C
// Brand: Subtropic, Model: YKR-H/102E remote
// Brand: Centek, Model: SCT-65Q09 A/C
// Brand: Centek, Model: YKR-P/002E remote

#ifndef IR_ELECTRA_H_
#define IR_ELECTRA_H_
Expand Down Expand Up @@ -37,7 +41,9 @@ union ElectraProtocol {
uint8_t :5;
uint8_t SwingH :3;
// Byte 3
uint8_t :8;
uint8_t :6;
uint8_t SensorUpdate :1;
uint8_t :1;
// Byte 4
uint8_t :5;
uint8_t Fan :3;
Expand All @@ -46,10 +52,12 @@ union ElectraProtocol {
uint8_t Turbo :1;
uint8_t :1;
// Byte 6
uint8_t :5;
uint8_t :3;
uint8_t IFeel :1;
uint8_t :1;
uint8_t Mode :3;
// Byte 7
uint8_t :8;
uint8_t SensorTemp :8;
// Byte 8
uint8_t :8;
// Byte 9
Expand Down Expand Up @@ -93,6 +101,11 @@ const uint8_t kElectraAcLightToggleMask = 0x11;
// and known OFF values of 0x08 (0b00001000) & 0x05 (0x00000101)
const uint8_t kElectraAcLightToggleOff = 0x08;

// Re: Byte[7]. Or Delta == 0xA and Temperature are stored in last 6 bits,
// and bit 7 stores Unknown flag
const uint8_t kElectraAcSensorTempDelta = 0x4A;
const uint8_t kElectraAcSensorMinTemp = 0; // 0C
const uint8_t kElectraAcSensorMaxTemp = 50; // 50C

// Classes
/// Class for handling detailed Electra A/C messages.
Expand Down Expand Up @@ -130,6 +143,12 @@ class IRElectraAc {
bool getLightToggle(void) const;
void setTurbo(const bool on);
bool getTurbo(void) const;
void setIFeel(const bool on);
bool getIFeel(void) const;
void setSensorUpdate(const bool on);
bool getSensorUpdate(void) const;
void setSensorTemp(const uint8_t temp);
uint8_t getSensorTemp(void) const;
uint8_t* getRaw(void);
void setRaw(const uint8_t new_code[],
const uint16_t length = kElectraAcStateLength);
Expand Down
3 changes: 2 additions & 1 deletion test/IRac_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,8 @@ TEST(TestIRac, Electra) {
IRrecv capture(kGpioUnused);
char expected[] =
"Power: On, Mode: 6 (Fan), Temp: 26C, Fan: 1 (High), "
"Swing(V): On, Swing(H): On, Light: Toggle, Clean: On, Turbo: On";
"Swing(V): On, Swing(H): On, Light: Toggle, Clean: On, Turbo: On, "
"IFeel: Off";

ac.begin();
irac.electra(&ac,
Expand Down
91 changes: 83 additions & 8 deletions test/ir_Electra_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ TEST(TestDecodeElectraAC, RealExampleDecode) {
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 24C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: Off",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t r, p;
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p));
Expand Down Expand Up @@ -235,23 +236,26 @@ TEST(TestIRElectraAcClass, HumanReadable) {
ac.setRaw(on_cool_32C_auto_voff);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 32C, Fan: 5 (Auto), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: Off",
ac.toString());
uint8_t on_cool_16C_auto_voff[13] = {
0xC3, 0x47, 0xE0, 0x00, 0xA0, 0x00, 0x20,
0x00, 0x00, 0x20, 0x00, 0x41, 0x0B};
ac.setRaw(on_cool_16C_auto_voff);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 16C, Fan: 5 (Auto), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: Off",
ac.toString());
uint8_t on_cool_16C_low_voff[13] = {
0xC3, 0x47, 0xE0, 0x00, 0x60, 0x00, 0x20,
0x00, 0x00, 0x20, 0x00, 0x41, 0xCB};
ac.setRaw(on_cool_16C_low_voff);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 16C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: Off",
ac.toString());
}

Expand All @@ -275,7 +279,8 @@ TEST(TestIRElectraAcClass, Clean) {
ac.setRaw(on);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 24C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off, "
"IFeel: Off",
ac.toString());
}

Expand All @@ -301,7 +306,8 @@ TEST(TestIRElectraAcClass, Turbo) {
ac.setRaw(on);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 24C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: On",
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: On, "
"IFeel: Off",
ac.toString());
}

Expand All @@ -325,7 +331,8 @@ TEST(TestIRElectraAcClass, LightToggle) {
ac.setRaw(on);
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 24C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off, "
"IFeel: Off",
ac.toString());
}

Expand All @@ -352,8 +359,76 @@ TEST(TestIRElectraAcClass, ConstructKnownState) {

EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 24C, Fan: 3 (Low), "
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off",
"Swing(V): Off, Swing(H): Off, Light: Toggle, Clean: On, Turbo: Off, "
"IFeel: Off",
ac.toString());
EXPECT_STATE_EQ(on_cool_24C_fan1_swing_off_turbo_off_clean_on,
ac.getRaw(), kElectraAcBits);
}

TEST(TestIRElectraAcClass, IFeelAndSensor) {
IRElectraAc ac(kGpioUnused);
ac.stateReset();
// Test a real example.
const uint8_t ifeel_on[kElectraAcStateLength] = {
0xC3, 0x6F, 0xE0, 0x00, 0xA0, 0x00, 0x28,
0x64, 0x00, 0x20, 0x00, 0x1E, 0x7C};
ac.setRaw(ifeel_on);
EXPECT_TRUE(ac.getIFeel());
EXPECT_EQ(26, ac.getSensorTemp());
EXPECT_EQ(
"Power: On, Mode: 1 (Cool), Temp: 21C, Fan: 5 (Auto), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: On, Sensor Temp: 26C",
ac.toString());

ac.stateReset();
EXPECT_FALSE(ac.getIFeel());
EXPECT_EQ(kElectraAcSensorMinTemp, ac.getSensorTemp());

ac.setIFeel(true);
EXPECT_TRUE(ac.getIFeel());
EXPECT_EQ(kElectraAcSensorMinTemp, ac.getSensorTemp());

ac.setSensorTemp(kElectraAcSensorMaxTemp);
EXPECT_EQ(kElectraAcSensorMaxTemp, ac.getSensorTemp());

ac.setSensorTemp(kElectraAcSensorMaxTemp + 1);
EXPECT_EQ(kElectraAcSensorMaxTemp, ac.getSensorTemp());

ac.setIFeel(false);
EXPECT_FALSE(ac.getIFeel());
EXPECT_EQ(kElectraAcSensorMinTemp, ac.getSensorTemp());
EXPECT_EQ(0, ac._.SensorTemp);

ac.setIFeel(true);
ac.setSensorTemp(kElectraAcSensorMinTemp);
EXPECT_TRUE(ac.getIFeel());
EXPECT_EQ(kElectraAcSensorMinTemp, ac.getSensorTemp());

ac.setSensorTemp(26); // Celsius
EXPECT_TRUE(ac.getIFeel());
EXPECT_EQ(26, ac.getSensorTemp());

EXPECT_FALSE(ac.getSensorUpdate());
ac.setSensorUpdate(true);
EXPECT_TRUE(ac.getSensorUpdate());
EXPECT_EQ("Sensor Temp: 26C", ac.toString());
ac.setSensorUpdate(false);
EXPECT_FALSE(ac.getSensorUpdate());

const uint8_t sensor_update_28C[kElectraAcStateLength] = {
0xC3, 0x9F, 0xE0, 0x40, 0xA0, 0x00, 0x88,
0x66, 0x00, 0x30, 0x00, 0x1E, 0x5E};
ac.setRaw(sensor_update_28C);
EXPECT_TRUE(ac.getSensorUpdate());
EXPECT_EQ(28, ac.getSensorTemp());
EXPECT_EQ("Sensor Temp: 28C", ac.toString());
ac.setSensorUpdate(false);
EXPECT_FALSE(ac.getSensorUpdate());
EXPECT_EQ(
"Power: On, Mode: 4 (Heat), Temp: 27C, Fan: 5 (Auto), "
"Swing(V): Off, Swing(H): Off, Light: -, Clean: Off, Turbo: Off, "
"IFeel: On, Sensor Temp: 28C",
ac.toString());
}