HAIER_AC160: Experimental detail support. (#1852)

* This is basically a cut & paste of the `IRHaierAC176` class.
  - Without some features. e.g. SwingH
  - Modified values for SwingV
* Rough unit test coverage for new code.
  - Needs more real data to confirm etc.
* Add in support required for `IRac` class to work with this protocol.
* Add Light support
  - Report the button press code.
  - Make a guess at how the setting works.
    - i.e. The button code toggles the light on and off.
  - Adjust `toCommon()` to handle previous state.
  - Handle the light toggles appropriately.
  - Add a unit test case.
* Add AuxHeating setting.
* Add support for setting/getting Health/Filter status.

Fixes #1804
This commit is contained in:
David Conran
2022-08-17 15:00:09 +10:00
committed by GitHub
parent 7a8cf9c6a2
commit 48f2db23dd
6 changed files with 1399 additions and 5 deletions

View File

@@ -219,6 +219,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) {
#if SEND_HAIER_AC
case decode_type_t::HAIER_AC:
#endif
#if SEND_HAIER_AC160
case decode_type_t::HAIER_AC160:
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
case decode_type_t::HAIER_AC176:
#endif // SEND_HAIER_AC176
@@ -1200,6 +1203,52 @@ void IRac::haier(IRHaierAC *ac,
}
#endif // SEND_HAIER_AC
#if SEND_HAIER_AC160
/// Send a Haier 160 bit A/C message with the supplied settings.
/// @param[in, out] ac A Ptr to an IRHaierAC160 object to use.
/// @param[in] on The power setting.
/// @param[in] mode The operation mode setting.
/// @param[in] celsius Temperature units. True is Celsius, False is Fahrenheit.
/// @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] turbo Run the device in turbo/powerful mode.
/// @param[in] quiet Run the device in quiet mode.
/// @param[in] filter Turn on the (ion/pollen/etc) filter mode.
/// @param[in] clean Turn on the clean mode.
/// @param[in] light Turn on the LED/Display mode.
/// @param[in] prevlight Previous LED/Display mode.
/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on.
void IRac::haier160(IRHaierAC160 *ac,
const bool on, const stdAc::opmode_t mode,
const bool celsius, const float degrees,
const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool turbo, const bool quiet, const bool filter,
const bool clean, const bool light, const bool prevlight,
const int16_t sleep) {
ac->begin();
// No Model setting available.
ac->setMode(ac->convertMode(mode));
ac->setUseFahrenheit(!celsius);
ac->setTemp(degrees);
ac->setFan(ac->convertFan(fan));
ac->setSwingV(ac->convertSwingV(swingv));
// No Horizontal Swing setting available.
ac->setQuiet(quiet);
ac->setTurbo(turbo);
ac->setHealth(filter);
ac->setClean(clean);
// No Clean setting available.
// No Beep setting available.
ac->setSleep(sleep >= 0); // Sleep on this A/C is either on or off.
ac->setPower(on);
// Light needs to be sent last as the "button" value seems to control it.
ac->setLightToggle(light ^ prevlight);
ac->send();
}
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
/// Send a Haier 176 bit A/C message with the supplied settings.
/// @param[in, out] ac A Ptr to an IRHaierAC176 object to use.
@@ -2764,6 +2813,9 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
const stdAc::swingv_t prev_swingv = (prev != NULL) ? prev->swingv
: stdAc::swingv_t::kOff;
#endif // (SEND_LG || SEND_SHARP_AC)
#if (SEND_HAIER_AC160)
const bool prev_light = (prev != NULL) ? prev->light : !send.light;
#endif // (SEND_HAIER_AC160)
#if SEND_MIDEA
const bool prev_quiet = (prev != NULL) ? prev->quiet : !send.quiet;
#endif // SEND_MIDEA
@@ -2978,6 +3030,16 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) {
break;
}
#endif // SEND_HAIER_AC
#if SEND_HAIER_AC160
case HAIER_AC160:
{
IRHaierAC160 ac(_pin, _inverted, _modulation);
haier160(&ac, send.power, send.mode, send.celsius, send.degrees,
send.fanspeed, send.swingv, send.turbo, send.filter, send.clean,
send.light, prev_light, send.sleep);
break;
}
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
case HAIER_AC176:
{
@@ -3876,6 +3938,13 @@ namespace IRAcUtils {
return ac.toString();
}
#endif // DECODE_HAIER_AC
#if DECODE_HAIER_AC160
case decode_type_t::HAIER_AC160: {
IRHaierAC160 ac(kGpioUnused);
ac.setRaw(result->state);
return ac.toString();
}
#endif // DECODE_HAIER_AC160
#if DECODE_HAIER_AC176
case decode_type_t::HAIER_AC176: {
IRHaierAC176 ac(kGpioUnused);
@@ -4351,6 +4420,14 @@ namespace IRAcUtils {
break;
}
#endif // DECODE_HAIER_AC
#if DECODE_HAIER_AC160
case decode_type_t::HAIER_AC160: {
IRHaierAC160 ac(kGpioUnused);
ac.setRaw(decode->state);
*result = ac.toCommon(prev);
break;
}
#endif // DECODE_HAIER_AC160
#if DECODE_HAIER_AC176
case decode_type_t::HAIER_AC176: {
IRHaierAC176 ac(kGpioUnused);

View File

@@ -275,6 +275,15 @@ void electra(IRElectraAc *ac,
const bool filter, const int16_t sleep = -1,
const int16_t clock = -1);
#endif // SEND_HAIER_AC
#if SEND_HAIER_AC160
void haier160(IRHaierAC160 *ac,
const bool on, const stdAc::opmode_t mode, const bool celsius,
const float degrees, const stdAc::fanspeed_t fan,
const stdAc::swingv_t swingv,
const bool turbo, const bool quiet, const bool filter,
const bool clean, const bool light, const bool prevlight,
const int16_t sleep = -1);
#endif // SEND_HAIER_AC160
#if SEND_HAIER_AC176
void haier176(IRHaierAC176 *ac,
const haier_ac176_remote_model_t model, const bool on,

View File

@@ -1481,3 +1481,689 @@ bool IRrecv::decodeHaierAC160(decode_results* results, uint16_t offset,
return true;
}
#endif // DECODE_HAIER_AC160
/// Class constructor
/// @param[in] pin GPIO to be used when sending.
/// @param[in] inverted Is the output signal to be inverted?
/// @param[in] use_modulation Is frequency modulation to be used?
IRHaierAC160::IRHaierAC160(const uint16_t pin, const bool inverted,
const bool use_modulation)
: _irsend(pin, inverted, use_modulation) { stateReset(); }
/// Set up hardware to be able to send a message.
void IRHaierAC160::begin(void) { _irsend.begin(); }
#if SEND_HAIER_AC160
/// Send the current internal state as an IR message.
/// @param[in] repeat Nr. of times the message will be repeated.
void IRHaierAC160::send(const uint16_t repeat) {
_irsend.sendHaierAC160(getRaw(), kHaierAC160StateLength, repeat);
}
#endif // SEND_HAIER_AC160
/// Calculate and set the checksum values for the internal state.
void IRHaierAC160::checksum(void) {
_.Sum = sumBytes(_.raw, kHaierACYRW02StateLength - 1);
_.Sum2 = sumBytes(_.raw + kHaierACYRW02StateLength,
kHaierAC160StateLength - kHaierACYRW02StateLength - 1);
}
/// Reset the internal state to a fixed known good state.
void IRHaierAC160::stateReset(void) {
std::memset(_.raw, 0, sizeof _.raw);
_.Model = kHaierAcYrw02ModelA;
_.Prefix = kHaierAc160Prefix;
_.Temp = kHaierAcYrw02DefTempC - kHaierAcYrw02MinTempC;
setClean(false);
setFan(kHaierAcYrw02FanAuto);
_.Power = true;
_.Button = kHaierAcYrw02ButtonPower;
}
/// Get a PTR to the internal state/code for this protocol.
/// @return PTR to a code for this protocol based on the current internal state.
uint8_t* IRHaierAC160::getRaw(void) {
checksum();
return _.raw;
}
/// Set the internal state from a valid code for this protocol.
/// @param[in] new_code A valid code for this protocol.
void IRHaierAC160::setRaw(const uint8_t new_code[]) {
memcpy(_.raw, new_code, kHaierAC160StateLength);
}
/// Set the Button/Command setting of the A/C.
/// @param[in] button The value of the button/command that was pressed.
void IRHaierAC160::setButton(uint8_t button) {
switch (button) {
case kHaierAcYrw02ButtonTempUp:
case kHaierAcYrw02ButtonTempDown:
case kHaierAcYrw02ButtonSwingV:
case kHaierAcYrw02ButtonSwingH:
case kHaierAcYrw02ButtonFan:
case kHaierAcYrw02ButtonPower:
case kHaierAcYrw02ButtonMode:
case kHaierAcYrw02ButtonHealth:
case kHaierAcYrw02ButtonTurbo:
case kHaierAcYrw02ButtonSleep:
case kHaierAcYrw02ButtonLock:
case kHaierAc160ButtonClean:
case kHaierAcYrw02ButtonCFAB:
_.Button = button;
}
}
/// Get the Button/Command setting of the A/C.
/// @return The value of the button/command that was pressed.
uint8_t IRHaierAC160::getButton(void) const { return _.Button; }
/// Set the operating mode of the A/C.
/// @param[in] mode The desired operating mode.
void IRHaierAC160::setMode(uint8_t mode) {
switch (mode) {
case kHaierAcYrw02Auto:
case kHaierAcYrw02Dry:
case kHaierAcYrw02Fan:
// Turbo & Quiet is only available in Cool/Heat mode.
_.Turbo = false;
_.Quiet = false;
// FALL-THRU
case kHaierAcYrw02Cool:
case kHaierAcYrw02Heat:
_.Button = kHaierAcYrw02ButtonMode;
_.Mode = mode;
break;
default:
setMode(kHaierAcYrw02Auto); // Unexpected, default to auto mode.
}
_.AuxHeating = (_.Mode == kHaierAcYrw02Heat); // Set only if heat mode.
}
/// Get the operating mode setting of the A/C.
/// @return The current operating mode setting.
uint8_t IRHaierAC160::getMode(void) const { return _.Mode; }
/// Set the default temperature units to use.
/// @param[in] on Use Fahrenheit as the units.
/// true is Fahrenheit, false is Celsius.
void IRHaierAC160::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; }
/// Get the default temperature units in use.
/// @return true is Fahrenheit, false is Celsius.
bool IRHaierAC160::getUseFahrenheit(void) const { return _.UseFahrenheit; }
/// Set the temperature.
/// @param[in] degree The temperature in degrees.
/// @param[in] fahrenheit Use units of Fahrenheit and set that as units used.
void IRHaierAC160::setTemp(const uint8_t degree, const bool fahrenheit) {
uint8_t old_temp = getTemp();
if (old_temp == degree) return;
if (_.UseFahrenheit == fahrenheit) {
if (old_temp > degree)
_.Button = kHaierAcYrw02ButtonTempDown;
else
_.Button = kHaierAcYrw02ButtonTempUp;
} else {
_.Button = kHaierAcYrw02ButtonCFAB;
}
_.UseFahrenheit = fahrenheit;
uint8_t temp = degree;
if (fahrenheit) {
if (temp < kHaierAcYrw02MinTempF)
temp = kHaierAcYrw02MinTempF;
else if (temp > kHaierAcYrw02MaxTempF)
temp = kHaierAcYrw02MaxTempF;
if (degree >= 77) { temp++; }
if (degree >= 79) { temp++; }
// See at IRHaierAC160::getTemp() comments for clarification
_.ExtraDegreeF = temp % 2;
_.Temp = (temp - kHaierAcYrw02MinTempF -_.ExtraDegreeF) >> 1;
} else {
if (temp < kHaierAcYrw02MinTempC)
temp = kHaierAcYrw02MinTempC;
else if (temp > kHaierAcYrw02MaxTempC)
temp = kHaierAcYrw02MaxTempC;
_.Temp = temp - kHaierAcYrw02MinTempC;
}
}
/// Get the current temperature setting.
/// The unit of temperature is specified by UseFahrenheit value.
/// @return The current setting for temperature.
uint8_t IRHaierAC160::getTemp(void) const {
if (!_.UseFahrenheit) { return _.Temp + kHaierAcYrw02MinTempC; }
uint8_t degree = _.Temp*2 + kHaierAcYrw02MinTempF + _.ExtraDegreeF;
// The way of coding the temperature in degree Fahrenheit is
// kHaierAcYrw02MinTempF + Temp*2 + ExtraDegreeF, for example
// Temp = 0b0011, ExtraDegreeF = 0b1, temperature is 60 + 3*2 + 1 = 67F
// But around 78F there is unconsistency, see table below
//
// | Fahrenheit | Temp | ExtraDegreeF |
// | 60F | 0b0000 | 0b0 |
// | 61F | 0b0000 | 0b1 |
// | 62F | 0b0001 | 0b0 |
// | 63F | 0b0001 | 0b1 |
// | 64F | 0b0010 | 0b0 |
// | 65F | 0b0010 | 0b1 |
// | 66F | 0b0011 | 0b0 |
// | 67F | 0b0011 | 0b1 |
// | 68F | 0b0100 | 0b0 |
// | 69F | 0b0100 | 0b1 |
// | 70F | 0b0101 | 0b0 |
// | 71F | 0b0101 | 0b1 |
// | 72F | 0b0110 | 0b0 |
// | 73F | 0b0110 | 0b1 |
// | 74F | 0b0111 | 0b0 |
// | 75F | 0b0111 | 0b1 |
// | 76F | 0b1000 | 0b0 |
// | Not Used | 0b1000 | 0b1 |
// | 77F | 0b1001 | 0b0 |
// | Not Used | 0b1001 | 0b1 |
// | 78F | 0b1010 | 0b0 |
// | 79F | 0b1010 | 0b1 |
// | 80F | 0b1011 | 0b0 |
// | 81F | 0b1011 | 0b1 |
// | 82F | 0b1100 | 0b0 |
// | 83F | 0b1100 | 0b1 |
// | 84F | 0b1101 | 0b0 |
// | 86F | 0b1110 | 0b0 |
// | 85F | 0b1101 | 0b1 |
if (degree >= 77) { degree--; }
if (degree >= 79) { degree--; }
return degree;
}
/// Set the Clean setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setClean(const bool on) {
_.Button = kHaierAc160ButtonClean;
_.Clean = on;
_.Clean2 = on;
}
/// Get the Clean setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getClean(void) const { return _.Clean && _.Clean2; }
/// Get the value of the current power setting.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getPower(void) const { return _.Power; }
/// Change the power setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setPower(const bool on) {
_.Button = kHaierAcYrw02ButtonPower;
_.Power = on;
}
/// Change the power setting to On.
void IRHaierAC160::on(void) { setPower(true); }
/// Change the power setting to Off.
void IRHaierAC160::off(void) { setPower(false); }
/// Get the Sleep setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getSleep(void) const { return _.Sleep; }
/// Set the Sleep setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setSleep(const bool on) {
_.Button = kHaierAcYrw02ButtonSleep;
_.Sleep = on;
}
/// Get the Turbo setting of the A/C.
/// @return The current turbo setting.
bool IRHaierAC160::getTurbo(void) const { return _.Turbo; }
/// Set the Turbo setting of the A/C.
/// @param[in] on The desired turbo setting.
/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode
void IRHaierAC160::setTurbo(const bool on) {
switch (getMode()) {
case kHaierAcYrw02Cool:
case kHaierAcYrw02Heat:
_.Turbo = on;
_.Button = kHaierAcYrw02ButtonTurbo;
if (on) _.Quiet = false;
}
}
/// Get the Quiet setting of the A/C.
/// @return The current Quiet setting.
bool IRHaierAC160::getQuiet(void) const { return _.Quiet; }
/// Set the Quiet setting of the A/C.
/// @param[in] on The desired Quiet setting.
/// @note Turbo & Quiet can't be on at the same time, and only in Heat/Cool mode
void IRHaierAC160::setQuiet(const bool on) {
switch (getMode()) {
case kHaierAcYrw02Cool:
case kHaierAcYrw02Heat:
_.Quiet = on;
_.Button = kHaierAcYrw02ButtonTurbo;
if (on) _.Turbo = false;
}
}
/// Get the value of the Aux Heating setting.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getAuxHeating(void) const { return _.AuxHeating; }
/// Change the Aux Heating setting.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setAuxHeating(const bool on) {
_.Button = kHaierAc160ButtonAuxHeating;
_.AuxHeating = on;
}
/// Get the value of the current Light toggle setting.
/// @return true, the setting is on. false, the setting is off.
/// @note This setting seems to be controlled just by the button setting.
bool IRHaierAC160::getLightToggle(void) const {
return _.Button == kHaierAc160ButtonLight;
}
/// Set the Light Toggle setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
/// @note This setting seems to be controlled just by the button setting.
void IRHaierAC160::setLightToggle(const bool on) {
_.Button = on ? kHaierAc160ButtonLight : kHaierAcYrw02ButtonPower;
}
/// Get the current fan speed setting.
/// @return The current fan speed.
uint8_t IRHaierAC160::getFan(void) const { return _.Fan; }
/// Set the speed of the fan.
/// @param[in] speed The desired setting.
void IRHaierAC160::setFan(uint8_t speed) {
switch (speed) {
case kHaierAcYrw02FanLow:
case kHaierAcYrw02FanMed:
case kHaierAcYrw02FanHigh:
case kHaierAcYrw02FanAuto:
_.Fan = speed;
_.Fan2 = (speed == kHaierAcYrw02FanAuto) ? 0 : speed;
_.Button = kHaierAcYrw02ButtonFan;
}
}
/// Set the Health (filter) setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setHealth(const bool on) {
_.Button = kHaierAcYrw02ButtonHealth;
_.Health = on;
}
/// Get the Health (filter) setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getHealth(void) const { return _.Health; }
/// Get the Vertical Swing position setting of the A/C.
/// @return The native position/mode.
uint8_t IRHaierAC160::getSwingV(void) const { return _.SwingV; }
/// Set the Vertical Swing mode of the A/C.
/// @param[in] pos The position/mode to set the vanes to.
void IRHaierAC160::setSwingV(const uint8_t pos) {
switch (pos) {
case kHaierAc160SwingVOff:
case kHaierAc160SwingVAuto:
case kHaierAc160SwingVTop:
case kHaierAc160SwingVHighest:
case kHaierAc160SwingVHigh:
case kHaierAc160SwingVMiddle:
case kHaierAc160SwingVLow:
case kHaierAc160SwingVLowest:
_.Button = kHaierAcYrw02ButtonSwingV;
_.SwingV = pos;
break;
default: return; // If in doubt, Do nothing.
}
}
/// Set the Timer operating mode.
/// @param[in] mode The timer mode to use.
void IRHaierAC160::setTimerMode(const uint8_t mode) {
_.TimerMode = (mode > kHaierAcYrw02OffThenOnTimer) ? kHaierAcYrw02NoTimers
: mode;
switch (_.TimerMode) {
case kHaierAcYrw02NoTimers:
setOnTimer(0); // Disable the On timer.
setOffTimer(0); // Disable the Off timer.
break;
case kHaierAcYrw02OffTimer:
setOnTimer(0); // Disable the On timer.
break;
case kHaierAcYrw02OnTimer:
setOffTimer(0); // Disable the Off timer.
break;
}
}
/// Get the Timer operating mode.
/// @return The mode of the timer is currently configured to.
uint8_t IRHaierAC160::getTimerMode(void) const { return _.TimerMode; }
/// Set the number of minutes of the On Timer setting.
/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer.
void IRHaierAC160::setOnTimer(const uint16_t mins) {
const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins);
_.OnTimerHrs = nr_mins / 60;
_.OnTimerMins = nr_mins % 60;
const bool enabled = (nr_mins > 0);
uint8_t mode = getTimerMode();
switch (mode) {
case kHaierAcYrw02OffTimer:
mode = enabled ? kHaierAcYrw02OffThenOnTimer : mode;
break;
case kHaierAcYrw02OnThenOffTimer:
case kHaierAcYrw02OffThenOnTimer:
mode = enabled ? kHaierAcYrw02OffThenOnTimer : kHaierAcYrw02OffTimer;
break;
default:
// Enable/Disable the On timer for the simple case.
mode = enabled << 1;
}
_.TimerMode = mode;
}
/// Get the number of minutes of the On Timer setting.
/// @return Nr of minutes.
uint16_t IRHaierAC160::getOnTimer(void) const {
return _.OnTimerHrs * 60 + _.OnTimerMins;
}
/// Set the number of minutes of the Off Timer setting.
/// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer.
void IRHaierAC160::setOffTimer(const uint16_t mins) {
const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins);
_.OffTimerHrs = nr_mins / 60;
_.OffTimerMins = nr_mins % 60;
const bool enabled = (nr_mins > 0);
uint8_t mode = getTimerMode();
switch (mode) {
case kHaierAcYrw02OnTimer:
mode = enabled ? kHaierAcYrw02OnThenOffTimer : mode;
break;
case kHaierAcYrw02OnThenOffTimer:
case kHaierAcYrw02OffThenOnTimer:
mode = enabled ? kHaierAcYrw02OnThenOffTimer : kHaierAcYrw02OnTimer;
break;
default:
// Enable/Disable the Off timer for the simple case.
mode = enabled;
}
_.TimerMode = mode;
}
/// Get the number of minutes of the Off Timer setting.
/// @return Nr of minutes.
uint16_t IRHaierAC160::getOffTimer(void) const {
return _.OffTimerHrs * 60 + _.OffTimerMins;
}
/// Get the Lock setting of the A/C.
/// @return true, the setting is on. false, the setting is off.
bool IRHaierAC160::getLock(void) const { return _.Lock; }
/// Set the Lock setting of the A/C.
/// @param[in] on true, the setting is on. false, the setting is off.
void IRHaierAC160::setLock(const bool on) {
_.Button = kHaierAcYrw02ButtonLock;
_.Lock = on;
}
/// 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.
uint8_t IRHaierAC160::convertMode(const stdAc::opmode_t mode) {
switch (mode) {
case stdAc::opmode_t::kCool: return kHaierAcYrw02Cool;
case stdAc::opmode_t::kHeat: return kHaierAcYrw02Heat;
case stdAc::opmode_t::kDry: return kHaierAcYrw02Dry;
case stdAc::opmode_t::kFan: return kHaierAcYrw02Fan;
default: return kHaierAcYrw02Auto;
}
}
/// Convert a stdAc::fanspeed_t enum into it's native speed.
/// @param[in] speed The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRHaierAC160::convertFan(const stdAc::fanspeed_t speed) {
switch (speed) {
case stdAc::fanspeed_t::kMin:
case stdAc::fanspeed_t::kLow: return kHaierAcYrw02FanLow;
case stdAc::fanspeed_t::kMedium: return kHaierAcYrw02FanMed;
case stdAc::fanspeed_t::kHigh:
case stdAc::fanspeed_t::kMax: return kHaierAcYrw02FanHigh;
default: return kHaierAcYrw02FanAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] position The enum to be converted.
/// @return The native equivalent of the enum.
uint8_t IRHaierAC160::convertSwingV(const stdAc::swingv_t position) {
switch (position) {
case stdAc::swingv_t::kHighest: return kHaierAc160SwingVTop;
case stdAc::swingv_t::kHigh: return kHaierAc160SwingVHigh;
case stdAc::swingv_t::kMiddle: return kHaierAc160SwingVMiddle;
case stdAc::swingv_t::kLow: return kHaierAc160SwingVLow;
case stdAc::swingv_t::kLowest: return kHaierAc160SwingVLowest;
case stdAc::swingv_t::kOff: return kHaierAc160SwingVOff;
default: return kHaierAc160SwingVAuto;
}
}
/// Convert a native mode into its stdAc equivalent.
/// @param[in] mode The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::opmode_t IRHaierAC160::toCommonMode(const uint8_t mode) {
switch (mode) {
case kHaierAcYrw02Cool: return stdAc::opmode_t::kCool;
case kHaierAcYrw02Heat: return stdAc::opmode_t::kHeat;
case kHaierAcYrw02Dry: return stdAc::opmode_t::kDry;
case kHaierAcYrw02Fan: return stdAc::opmode_t::kFan;
default: return stdAc::opmode_t::kAuto;
}
}
/// Convert a native fan speed into its stdAc equivalent.
/// @param[in] speed The native setting to be converted.
/// @return The stdAc equivalent of the native setting.
stdAc::fanspeed_t IRHaierAC160::toCommonFanSpeed(const uint8_t speed) {
switch (speed) {
case kHaierAcYrw02FanHigh: return stdAc::fanspeed_t::kMax;
case kHaierAcYrw02FanMed: return stdAc::fanspeed_t::kMedium;
case kHaierAcYrw02FanLow: return stdAc::fanspeed_t::kMin;
default: return stdAc::fanspeed_t::kAuto;
}
}
/// Convert a stdAc::swingv_t enum into it's native setting.
/// @param[in] pos The enum to be converted.
/// @return The native equivalent of the enum.
stdAc::swingv_t IRHaierAC160::toCommonSwingV(const uint8_t pos) {
switch (pos) {
case kHaierAc160SwingVTop:
case kHaierAc160SwingVHighest: return stdAc::swingv_t::kHighest;
case kHaierAc160SwingVHigh: return stdAc::swingv_t::kHigh;
case kHaierAc160SwingVMiddle: return stdAc::swingv_t::kMiddle;
case kHaierAc160SwingVLow: return stdAc::swingv_t::kLow;
case kHaierAc160SwingVLowest: return stdAc::swingv_t::kLowest;
case kHaierAc160SwingVOff: return stdAc::swingv_t::kOff;
default: return stdAc::swingv_t::kAuto;
}
}
/// 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.
stdAc::state_t IRHaierAC160::toCommon(const stdAc::state_t *prev) const {
stdAc::state_t result{};
// Start with the previous state if given it.
if (prev != NULL) {
result = *prev;
} else {
// Set defaults for non-zero values that are not implicitly set for when
// there is no previous state.
// e.g. Any setting that toggles should probably go here.
result.light = false;
}
result.protocol = decode_type_t::HAIER_AC160;
result.power = _.Power;
result.mode = toCommonMode(_.Mode);
result.celsius = !_.UseFahrenheit;
result.degrees = getTemp();
result.fanspeed = toCommonFanSpeed(_.Fan);
result.swingv = toCommonSwingV(_.SwingV);
result.swingh = stdAc::swingh_t::kOff;
result.sleep = _.Sleep ? 0 : -1;
result.turbo = _.Turbo;
result.quiet = _.Quiet;
result.clean = _.Clean && _.Clean2;
result.light ^= getLightToggle();
result.filter = _.Health;
// Not supported.
result.model = -1;
result.econo = false;
result.beep = true;
result.clock = -1;
return result;
}
/// Convert the current internal state into a human readable string.
/// @return A human readable string.
String IRHaierAC160::toString(void) const {
String result = "";
result.reserve(280); // Reserve some heap for the string to reduce fragging.
result += addBoolToString(_.Power, kPowerStr, false);
uint8_t cmd = _.Button;
result += addIntToString(cmd, kButtonStr);
result += kSpaceLBraceStr;
switch (cmd) {
case kHaierAcYrw02ButtonPower:
result += kPowerStr;
break;
case kHaierAcYrw02ButtonMode:
result += kModeStr;
break;
case kHaierAcYrw02ButtonFan:
result += kFanStr;
break;
case kHaierAcYrw02ButtonTempUp:
result += kTempUpStr;
break;
case kHaierAcYrw02ButtonTempDown:
result += kTempDownStr;
break;
case kHaierAcYrw02ButtonSleep:
result += kSleepStr;
break;
case kHaierAcYrw02ButtonHealth:
result += kHealthStr;
break;
case kHaierAcYrw02ButtonSwingV:
result += kSwingVStr;
break;
case kHaierAcYrw02ButtonSwingH:
result += kSwingHStr;
break;
case kHaierAcYrw02ButtonTurbo:
result += kTurboStr;
break;
case kHaierAcYrw02ButtonTimer:
result += kTimerStr;
break;
case kHaierAcYrw02ButtonLock:
result += kLockStr;
break;
case kHaierAc160ButtonClean:
result += kCleanStr;
break;
case kHaierAc160ButtonLight:
result += kLightStr;
break;
case kHaierAc160ButtonAuxHeating:
result += kHeatingStr;
break;
case kHaierAcYrw02ButtonCFAB:
result += kCelsiusFahrenheitStr;
break;
default:
result += kUnknownStr;
}
result += ')';
result += addModeToString(_.Mode, kHaierAcYrw02Auto, kHaierAcYrw02Cool,
kHaierAcYrw02Heat, kHaierAcYrw02Dry,
kHaierAcYrw02Fan);
result += addTempToString(getTemp(), !_.UseFahrenheit);
result += addFanToString(_.Fan, kHaierAcYrw02FanHigh, kHaierAcYrw02FanLow,
kHaierAcYrw02FanAuto, kHaierAcYrw02FanAuto,
kHaierAcYrw02FanMed);
result += addBoolToString(_.Turbo, kTurboStr);
result += addBoolToString(_.Quiet, kQuietStr);
result += addBoolToString(_.Health, kHealthStr);
result += addIntToString(_.SwingV, kSwingVStr);
result += kSpaceLBraceStr;
switch (_.SwingV) {
case kHaierAc160SwingVOff: result += kOffStr; break;
case kHaierAc160SwingVAuto: result += kAutoStr; break;
case kHaierAc160SwingVTop: result += kTopStr; break;
case kHaierAc160SwingVHighest: result += kHighestStr; break;
case kHaierAc160SwingVHigh: result += kHighStr; break;
case kHaierAc160SwingVMiddle: result += kMiddleStr; break;
case kHaierAc160SwingVLow: result += kLowStr; break;
case kHaierAc160SwingVLowest: result += kLowestStr; break;
default: result += kUnknownStr;
}
result += ')';
result += addBoolToString(_.Sleep, kSleepStr);
result += addBoolToString(getClean(), kCleanStr);
const uint8_t tmode = getTimerMode();
result += addIntToString(tmode, kTimerModeStr);
result += kSpaceLBraceStr;
switch (tmode) {
case kHaierAcYrw02NoTimers:
result += kNAStr;
break;
case kHaierAcYrw02OnTimer:
result += kOnStr;
break;
case kHaierAcYrw02OffTimer:
result += kOffStr;
break;
case kHaierAcYrw02OnThenOffTimer:
result += kOnStr;
result += '-';
result += kOffStr;
break;
case kHaierAcYrw02OffThenOnTimer:
result += kOffStr;
result += '-';
result += kOnStr;
break;
default:
result += kUnknownStr;
}
result += ')';
result += addLabeledString((tmode != kHaierAcYrw02NoTimers &&
tmode != kHaierAcYrw02OffTimer) ?
minsToString(getOnTimer()) : kOffStr, kOnTimerStr);
result += addLabeledString((tmode != kHaierAcYrw02NoTimers &&
tmode != kHaierAcYrw02OnTimer) ?
minsToString(getOffTimer()) : kOffStr, kOffTimerStr);
result += addBoolToString(_.Lock, kLockStr);
result += addBoolToString(_.AuxHeating, kHeatingStr);
return result;
}
// End of IRHaierAC160 class.

View File

@@ -146,6 +146,7 @@ const uint8_t kHaierAcYrw02DefTempC = 25;
const uint8_t kHaierAcYrw02ModelA = 0xA6;
const uint8_t kHaierAcYrw02ModelB = 0x59;
const uint8_t kHaierAc176Prefix = 0xB7;
const uint8_t kHaierAc160Prefix = 0xB5;
const uint8_t kHaierAcYrw02SwingVOff = 0x0;
const uint8_t kHaierAcYrw02SwingVTop = 0x1;
@@ -154,6 +155,15 @@ const uint8_t kHaierAcYrw02SwingVBottom = 0x3; // Only available in heat mode.
const uint8_t kHaierAcYrw02SwingVDown = 0xA;
const uint8_t kHaierAcYrw02SwingVAuto = 0xC; // Airflow
const uint8_t kHaierAc160SwingVOff = 0b0000;
const uint8_t kHaierAc160SwingVTop = 0b0001;
const uint8_t kHaierAc160SwingVHighest = 0b0010;
const uint8_t kHaierAc160SwingVHigh = 0b0100;
const uint8_t kHaierAc160SwingVMiddle = 0b0110;
const uint8_t kHaierAc160SwingVLow = 0b1000;
const uint8_t kHaierAc160SwingVLowest = 0b0011;
const uint8_t kHaierAc160SwingVAuto = 0b1100; // Airflow
const uint8_t kHaierAcYrw02SwingHMiddle = 0x0;
const uint8_t kHaierAcYrw02SwingHLeftMax = 0x3;
const uint8_t kHaierAcYrw02SwingHLeft = 0x4;
@@ -184,6 +194,9 @@ const uint8_t kHaierAcYrw02ButtonTurbo = 0b01000;
const uint8_t kHaierAcYrw02ButtonSleep = 0b01011;
const uint8_t kHaierAcYrw02ButtonTimer = 0b10000;
const uint8_t kHaierAcYrw02ButtonLock = 0b10100;
const uint8_t kHaierAc160ButtonLight = 0b10101;
const uint8_t kHaierAc160ButtonAuxHeating = 0b10110;
const uint8_t kHaierAc160ButtonClean = 0b11001;
const uint8_t kHaierAcYrw02ButtonCFAB = 0b11010;
const uint8_t kHaierAcYrw02NoTimers = 0b000;
@@ -262,6 +275,75 @@ union HaierAc176Protocol{
};
};
/// Native representation of a Haier 160 bit A/C message.
union HaierAc160Protocol{
uint8_t raw[kHaierAC160StateLength]; ///< The state in native form
struct {
// Byte 0
uint8_t Model :8;
// Byte 1
uint8_t SwingV :4;
uint8_t Temp :4; // 16C~30C
// Byte 2
uint8_t :5;
uint8_t SwingH :3;
// Byte 3
uint8_t :1;
uint8_t Health :1;
uint8_t :3;
uint8_t TimerMode :3;
// Byte 4
uint8_t :6;
uint8_t Power :1;
uint8_t AuxHeating :1;
// Byte 5
uint8_t OffTimerHrs :5;
uint8_t Fan :3;
// Byte 6
uint8_t OffTimerMins:6;
uint8_t Turbo :1;
uint8_t Quiet :1;
// Byte 7
uint8_t OnTimerHrs :5;
uint8_t Mode :3;
// Byte 8
uint8_t OnTimerMins :6;
uint8_t :1;
uint8_t Sleep :1;
// Byte 9
uint8_t :8;
// Byte 10
uint8_t ExtraDegreeF :1;
uint8_t :3;
uint8_t Clean :1;
uint8_t UseFahrenheit:1;
uint8_t :2;
// Byte 11
uint8_t :8;
// Byte 12
uint8_t Button :5;
uint8_t Lock :1;
uint8_t :2;
// Byte 13
uint8_t Sum :8;
// Byte 14
uint8_t Prefix :8;
// Byte 15
uint8_t :6;
uint8_t Clean2 :1;
uint8_t :1;
// Byte 16
uint8_t :5;
uint8_t Fan2 :3;
// Byte 17
uint8_t :8;
// Byte 18
uint8_t :8;
// Byte 19
uint8_t Sum2 :8;
};
};
// Legacy Haier YRW02 remote defines.
#define HAIER_AC_YRW02_SWING_OFF kHaierAcYrw02SwingOff
#define HAIER_AC_YRW02_SWING_TOP kHaierAcYrw02SwingTop
@@ -476,4 +558,96 @@ class IRHaierACYRW02 : public IRHaierAC176 {
const uint8_t state[],
const uint16_t length = kHaierACYRW02StateLength);
};
/// Class for handling detailed Haier 160 bit A/C messages.
class IRHaierAC160 {
public:
explicit IRHaierAC160(const uint16_t pin, const bool inverted = false,
const bool use_modulation = true);
#if SEND_HAIER_AC160
virtual void send(const uint16_t repeat = kHaierAc160DefaultRepeat);
/// Run the calibration to calculate uSec timing offsets for this platform.
/// @return The uSec timing offset needed per modulation of the IR Led.
/// @note This will produce a 65ms IR signal pulse at 38kHz.
/// Only ever needs to be run once per object instantiation, if at all.
int8_t calibrate(void) { return _irsend.calibrate(); }
#endif // SEND_HAIER_AC160
void begin(void);
void stateReset(void);
void setButton(const uint8_t button);
uint8_t getButton(void) const;
void setUseFahrenheit(const bool on);
bool getUseFahrenheit(void) const;
void setTemp(const uint8_t temp, const bool fahrenheit = false);
uint8_t getTemp(void) const;
void setFan(const uint8_t speed);
uint8_t getFan(void) const;
uint8_t getMode(void) const;
void setMode(const uint8_t mode);
bool getPower(void) const;
void setPower(const bool on);
void on(void);
void off(void);
bool getSleep(void) const;
void setSleep(const bool on);
bool getClean(void) const;
void setClean(const bool on);
bool getLightToggle(void) const;
void setLightToggle(const bool on);
bool getTurbo(void) const;
void setTurbo(const bool on);
bool getQuiet(void) const;
void setQuiet(const bool on);
bool getAuxHeating(void) const;
void setAuxHeating(const bool on);
uint8_t getSwingV(void) const;
void setSwingV(const uint8_t pos);
void setTimerMode(const uint8_t setting);
uint8_t getTimerMode(void) const;
void setOnTimer(const uint16_t mins);
uint16_t getOnTimer(void) const;
void setOffTimer(const uint16_t mins);
uint16_t getOffTimer(void) const;
bool getLock(void) const;
void setLock(const bool on);
bool getHealth(void) const;
void setHealth(const bool on);
uint8_t* getRaw(void);
virtual void setRaw(const uint8_t new_code[]);
static bool validChecksum(const uint8_t state[],
const uint16_t length = kHaierAC160StateLength);
static uint8_t convertMode(const stdAc::opmode_t mode);
static uint8_t convertFan(const stdAc::fanspeed_t speed);
static uint8_t convertSwingV(const stdAc::swingv_t position);
static stdAc::opmode_t toCommonMode(const uint8_t mode);
static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed);
static stdAc::swingv_t toCommonSwingV(const uint8_t pos);
static bool toCommonTurbo(const uint8_t speed);
static bool toCommonQuiet(const uint8_t speed);
stdAc::state_t toCommon(const stdAc::state_t *prev = NULL) const;
String toString(void) const;
#ifndef UNIT_TEST
private:
IRsend _irsend; ///< Instance of the IR send class
#else // UNIT_TEST
/// @cond IGNORE
IRsendTest _irsend; ///< Instance of the testing IR send class
/// @endcond
#endif // UNIT_TEST
HaierAc160Protocol _;
void checksum(void);
};
#endif // IR_HAIER_H_

View File

@@ -820,6 +820,41 @@ TEST(TestIRac, Haier) {
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));
}
TEST(TestIRac, Haier160) {
IRHaierAC160 ac(kGpioUnused);
IRac irac(kGpioUnused);
IRrecv capture(kGpioUnused);
const char expected[] =
"Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 23C, "
"Fan: 2 (Medium), Turbo: On, Quiet: Off, Health: On, "
"Swing(V): 4 (High), Sleep: On, "
"Clean: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off, "
"Lock: Off, Heating: Off";
ac.begin();
irac.haier160(&ac,
true, // Power
stdAc::opmode_t::kCool, // Mode
true, // Celsius
23, // Degrees
stdAc::fanspeed_t::kMedium, // Fan speed
stdAc::swingv_t::kHigh, // Vertical swing
true, // Turbo
false, // Quiet
true, // Filter/Health
true, // Clean
true, // Light
true, // Light (prev)
8 * 60 + 0); // Sleep time
ASSERT_EQ(expected, ac.toString());
ac._irsend.makeDecodeResult();
EXPECT_TRUE(capture.decode(&ac._irsend.capture));
ASSERT_EQ(HAIER_AC160, ac._irsend.capture.decode_type);
ASSERT_EQ(kHaierAC160Bits, 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));
}
TEST(TestIRac, Haier176) {
IRHaierAC176 ac(kGpioUnused);
IRac irac(kGpioUnused);

View File

@@ -1361,7 +1361,7 @@ TEST(TestUtils, Housekeeping) {
ASSERT_EQ("HAIER_AC160", typeToString(decode_type_t::HAIER_AC160));
ASSERT_EQ(decode_type_t::HAIER_AC160, strToDecodeType("HAIER_AC160"));
ASSERT_TRUE(hasACState(decode_type_t::HAIER_AC160));
ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::HAIER_AC160));
ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::HAIER_AC160));
ASSERT_EQ(kHaierAC160Bits, IRsend::defaultBits(decode_type_t::HAIER_AC160));
ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::HAIER_AC160));
}
@@ -1588,10 +1588,14 @@ TEST(TestDecodeHaierAC160, RealExample) {
EXPECT_FALSE(irsend.capture.repeat);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"",
"Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 26C, Fan: 3 (Low), "
"Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), Sleep: Off, "
"Clean: Off, "
"Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off, Lock: Off, "
"Heating: Off",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t result, prev;
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
}
// Decoding a message we entirely constructed based solely on a given state.
@@ -1613,8 +1617,417 @@ TEST(TestDecodeHaierAC160, SyntheticExample) {
EXPECT_FALSE(irsend.capture.repeat);
EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits);
EXPECT_EQ(
"",
"Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 26C, Fan: 3 (Low), "
"Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), Sleep: Off, "
"Clean: Off, "
"Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off, Lock: Off, "
"Heating: Off",
IRAcUtils::resultAcToString(&irsend.capture));
stdAc::state_t result, prev;
ASSERT_FALSE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev));
}
// Tests for the IRHaierAC160 class.
TEST(TestHaierAC160Class, Button) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setButton(kHaierAcYrw02ButtonPower);
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonMode);
EXPECT_EQ(kHaierAcYrw02ButtonMode, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonSleep);
EXPECT_EQ(kHaierAcYrw02ButtonSleep, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonFan);
// Test unexpected values.
ac.setButton(0xFF);
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
ac.setButton(0x10);
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
}
TEST(TestHaierAC160Class, OperatingMode) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setButton(kHaierAcYrw02ButtonPower);
ac.setMode(kHaierAcYrw02Auto);
EXPECT_EQ(kHaierAcYrw02Auto, ac.getMode());
EXPECT_EQ(kHaierAcYrw02ButtonMode, ac.getButton());
ac.setMode(kHaierAcYrw02Cool);
EXPECT_EQ(kHaierAcYrw02Cool, ac.getMode());
EXPECT_FALSE(ac.getAuxHeating());
ac.setMode(kHaierAcYrw02Heat);
EXPECT_EQ(kHaierAcYrw02Heat, ac.getMode());
EXPECT_TRUE(ac.getAuxHeating());
ac.setMode(kHaierAcYrw02Fan);
EXPECT_EQ(kHaierAcYrw02Fan, ac.getMode());
EXPECT_FALSE(ac.getAuxHeating());
ac.setMode(kHaierAcYrw02Dry);
EXPECT_EQ(kHaierAcYrw02Dry, ac.getMode());
ac.setMode(kHaierAcYrw02Auto - 1);
EXPECT_EQ(kHaierAcYrw02Auto, ac.getMode());
ac.setMode(kHaierAcYrw02Cool);
EXPECT_EQ(kHaierAcYrw02Cool, ac.getMode());
ac.setMode(kHaierAcYrw02Fan + 1);
EXPECT_EQ(kHaierAcYrw02Auto, ac.getMode());
ac.setMode(255);
EXPECT_EQ(kHaierAcYrw02Auto, ac.getMode());
}
TEST(TestHaierAC160Class, Temperature) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setTemp(kHaierAcYrw02MinTempC);
EXPECT_EQ(kHaierAcYrw02MinTempC, ac.getTemp());
ac.setButton(kHaierAcYrw02ButtonPower);
ac.setTemp(kHaierAcYrw02MinTempC + 1);
EXPECT_EQ(kHaierAcYrw02MinTempC + 1, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(kHaierAcYrw02MaxTempC);
EXPECT_EQ(kHaierAcYrw02MaxTempC, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(kHaierAcYrw02MinTempC - 1);
EXPECT_EQ(kHaierAcYrw02MinTempC, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempDown, ac.getButton());
ac.setTemp(kHaierAcYrw02MaxTempC + 1);
EXPECT_EQ(kHaierAcYrw02MaxTempC, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(23);
EXPECT_EQ(23, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempDown, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonPower);
ac.setTemp(23);
EXPECT_EQ(23, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
ac.setTemp(kHaierAcYrw02MinTempF, true);
EXPECT_EQ(kHaierAcYrw02MinTempF, ac.getTemp());
ac.setButton(kHaierAcYrw02ButtonPower);
ac.setTemp(kHaierAcYrw02MinTempF + 1, true);
EXPECT_EQ(kHaierAcYrw02MinTempF + 1, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(kHaierAcYrw02MaxTempF, true);
EXPECT_EQ(kHaierAcYrw02MaxTempF, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(kHaierAcYrw02MinTempF - 1, true);
EXPECT_EQ(kHaierAcYrw02MinTempF, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempDown, ac.getButton());
ac.setTemp(kHaierAcYrw02MaxTempF + 1, true);
EXPECT_EQ(kHaierAcYrw02MaxTempF, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
ac.setTemp(66, true);
EXPECT_EQ(66, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempDown, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonPower);
ac.setTemp(66, true);
EXPECT_EQ(66, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
// Test specific cases for converting to Fahrenheit
ac.setTemp(76, true);
EXPECT_EQ(76, ac.getTemp());
ac.setTemp(77, true);
EXPECT_EQ(77, ac.getTemp());
ac.setTemp(78, true);
EXPECT_EQ(78, ac.getTemp());
ac.setTemp(24);
EXPECT_EQ(kHaierAcYrw02ButtonCFAB, ac.getButton());
ac.setTemp(0);
EXPECT_EQ(kHaierAcYrw02MinTempC, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempDown, ac.getButton());
ac.setTemp(255);
EXPECT_EQ(kHaierAcMaxTemp, ac.getTemp());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
}
TEST(TestHaierAC160Class, CleanMode) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setClean(true);
EXPECT_TRUE(ac.getClean());
EXPECT_EQ(kHaierAc160ButtonClean, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setClean(false);
EXPECT_FALSE(ac.getClean());
EXPECT_EQ(kHaierAc160ButtonClean, ac.getButton());
ac.setClean(true);
EXPECT_TRUE(ac.getClean());
EXPECT_EQ(kHaierAc160ButtonClean, ac.getButton());
ac.stateReset();
EXPECT_FALSE(ac.getClean());
// clean button pressed.
// https://docs.google.com/spreadsheets/d/1RNJ7esbArS5fy1lmiM-i1PekXSNojCMad4WuuyunsC8/edit#gid=2048081808&range=FR22
const uint8_t clean_on[kHaierAC160StateLength] = {
0xA6, 0xAC, 0x00, 0x00, 0x40, 0x60, 0x00, 0x20, 0x00, 0x00,
0x10, 0x00, 0x19, 0x3B, 0xB5, 0x40, 0x60, 0x00, 0x00, 0x55};
ac.setRaw(clean_on);
EXPECT_TRUE(ac.getClean());
EXPECT_EQ(
"Power: On, Button: 25 (Clean), Mode: 1 (Cool), Temp: 26C, "
"Fan: 3 (Low), Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), "
"Sleep: Off, Clean: On, Timer Mode: 0 (N/A), "
"On Timer: Off, Off Timer: Off, Lock: Off, Heating: Off",
ac.toString());
// No clean set.
// https://docs.google.com/spreadsheets/d/1RNJ7esbArS5fy1lmiM-i1PekXSNojCMad4WuuyunsC8/edit#gid=2048081808&range=FR4
const uint8_t clean_off[kHaierAC160StateLength] = {
0xA6, 0xAC, 0x00, 0x00, 0x40, 0x60, 0x00, 0x20, 0x00, 0x00,
0x00, 0x00, 0x05, 0x17, 0xB5, 0x00, 0x60, 0x00, 0x00, 0x15};
ac.setRaw(clean_off);
EXPECT_FALSE(ac.getClean());
EXPECT_EQ(
"Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 26C, "
"Fan: 3 (Low), Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), "
"Sleep: Off, Clean: Off, Timer Mode: 0 (N/A), "
"On Timer: Off, Off Timer: Off, Lock: Off, Heating: Off",
ac.toString());
}
TEST(TestHaierAC160Class, Power) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setPower(true);
EXPECT_TRUE(ac.getPower());
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setPower(false);
EXPECT_FALSE(ac.getPower());
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
ac.setPower(true);
EXPECT_TRUE(ac.getPower());
EXPECT_EQ(kHaierAcYrw02ButtonPower, ac.getButton());
ac.off();
EXPECT_FALSE(ac.getPower());
ac.on();
EXPECT_TRUE(ac.getPower());
}
TEST(TestHaierAC160Class, SleepMode) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setSleep(true);
EXPECT_TRUE(ac.getSleep());
EXPECT_EQ(kHaierAcYrw02ButtonSleep, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setSleep(false);
EXPECT_FALSE(ac.getSleep());
EXPECT_EQ(kHaierAcYrw02ButtonSleep, ac.getButton());
ac.setSleep(true);
EXPECT_TRUE(ac.getSleep());
EXPECT_EQ(kHaierAcYrw02ButtonSleep, ac.getButton());
}
TEST(TestHaierAC160Class, Health) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setHealth(true);
EXPECT_TRUE(ac.getHealth());
EXPECT_EQ(kHaierAcYrw02ButtonHealth, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setHealth(false);
EXPECT_FALSE(ac.getHealth());
EXPECT_EQ(kHaierAcYrw02ButtonHealth, ac.getButton());
ac.setHealth(true);
EXPECT_TRUE(ac.getHealth());
EXPECT_EQ(kHaierAcYrw02ButtonHealth, ac.getButton());
}
TEST(TestHaierAC160Class, TurboAndQuiet) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setMode(kHaierAcYrw02Cool); // Turbo & Quiet is allowed in this mode.
ac.setTurbo(false);
ac.setQuiet(false);
EXPECT_FALSE(ac.getTurbo());
EXPECT_FALSE(ac.getQuiet());
EXPECT_EQ(kHaierAcYrw02ButtonTurbo, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setTurbo(true);
EXPECT_TRUE(ac.getTurbo());
EXPECT_FALSE(ac.getQuiet());
EXPECT_EQ(kHaierAcYrw02ButtonTurbo, ac.getButton());
ac.setQuiet(true);
EXPECT_FALSE(ac.getTurbo());
EXPECT_TRUE(ac.getQuiet());
EXPECT_EQ(kHaierAcYrw02ButtonTurbo, ac.getButton());
ac.setTurbo(false);
ac.setQuiet(false);
EXPECT_FALSE(ac.getTurbo());
EXPECT_FALSE(ac.getQuiet());
EXPECT_EQ(kHaierAcYrw02ButtonTurbo, ac.getButton());
ac.setMode(kHaierAcYrw02Auto); // Turbo & Quiet is not allowed in this mode.
EXPECT_FALSE(ac.getTurbo());
EXPECT_FALSE(ac.getQuiet());
ac.setTurbo(true);
EXPECT_FALSE(ac.getTurbo());
EXPECT_NE(kHaierAcYrw02ButtonTurbo, ac.getButton());
ac.setQuiet(true);
EXPECT_FALSE(ac.getQuiet());
EXPECT_NE(kHaierAcYrw02ButtonTurbo, ac.getButton());
}
TEST(TestHaierAC160Class, Fan) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setFan(kHaierAcYrw02FanAuto);
EXPECT_EQ(kHaierAcYrw02FanAuto, ac.getFan());
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setFan(kHaierAcYrw02FanLow);
EXPECT_EQ(kHaierAcYrw02FanLow, ac.getFan());
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
ac.setFan(kHaierAcYrw02FanHigh);
EXPECT_EQ(kHaierAcYrw02FanHigh, ac.getFan());
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
ac.setFan(kHaierAcYrw02FanMed);
EXPECT_EQ(kHaierAcYrw02FanMed, ac.getFan());
EXPECT_EQ(kHaierAcYrw02ButtonFan, ac.getButton());
// Test unexpected values.
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setFan(0x00);
EXPECT_EQ(kHaierAcYrw02FanMed, ac.getFan());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
}
TEST(TestHaierAC160Class, SwingV) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setSwingV(kHaierAc160SwingVOff);
EXPECT_EQ(kHaierAc160SwingVOff, ac.getSwingV());
EXPECT_EQ(kHaierAcYrw02ButtonSwingV, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setSwingV(kHaierAc160SwingVAuto);
EXPECT_EQ(kHaierAc160SwingVAuto, ac.getSwingV());
EXPECT_EQ(kHaierAcYrw02ButtonSwingV, ac.getButton());
ac.setSwingV(kHaierAc160SwingVTop);
EXPECT_EQ(kHaierAc160SwingVTop, ac.getSwingV());
EXPECT_EQ(kHaierAcYrw02ButtonSwingV, ac.getButton());
ac.setSwingV(kHaierAc160SwingVLow);
EXPECT_EQ(kHaierAc160SwingVLow, ac.getSwingV());
EXPECT_EQ(kHaierAcYrw02ButtonSwingV, ac.getButton());
// Test unexpected values.
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setSwingV(0xFF);
EXPECT_EQ(kHaierAc160SwingVLow, ac.getSwingV());
EXPECT_EQ(kHaierAcYrw02ButtonTempUp, ac.getButton());
}
TEST(TestHaierAC160Class, Light) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setLightToggle(true);
EXPECT_TRUE(ac.getLightToggle());
EXPECT_EQ(kHaierAc160ButtonLight, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setLightToggle(false);
EXPECT_FALSE(ac.getLightToggle());
EXPECT_NE(kHaierAc160ButtonLight, ac.getButton());
ac.setLightToggle(true);
EXPECT_TRUE(ac.getLightToggle());
EXPECT_EQ(kHaierAc160ButtonLight, ac.getButton());
const uint8_t light_press[kHaierAC160StateLength] = {
0xA6, 0xAC, 0x00, 0x00, 0x40, 0x60, 0x00, 0x20, 0x00, 0x00,
0x00, 0x00, 0x15, 0x27, 0xB5, 0x00, 0x60, 0x00, 0x00, 0x15};
ac.setRaw(light_press);
EXPECT_TRUE(ac.getLightToggle());
EXPECT_EQ(kHaierAc160ButtonLight, ac.getButton());
EXPECT_EQ(
"Power: On, Button: 21 (Light), Mode: 1 (Cool), Temp: 26C, "
"Fan: 3 (Low), Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), "
"Sleep: Off, Clean: Off, Timer Mode: 0 (N/A), "
"On Timer: Off, Off Timer: Off, Lock: Off, Heating: Off",
ac.toString());
}
TEST(TestHaierAC160Class, AuxHeating) {
IRHaierAC160 ac(kGpioUnused);
ac.begin();
ac.setAuxHeating(true);
EXPECT_TRUE(ac.getAuxHeating());
EXPECT_EQ(kHaierAc160ButtonAuxHeating, ac.getButton());
ac.setButton(kHaierAcYrw02ButtonTempUp);
ac.setAuxHeating(false);
EXPECT_FALSE(ac.getAuxHeating());
EXPECT_EQ(kHaierAc160ButtonAuxHeating, ac.getButton());
ac.setAuxHeating(true);
EXPECT_TRUE(ac.getAuxHeating());
EXPECT_EQ(kHaierAc160ButtonAuxHeating, ac.getButton());
// https://docs.google.com/spreadsheets/d/1RNJ7esbArS5fy1lmiM-i1PekXSNojCMad4WuuyunsC8/edit#gid=2048081808&range=A124:W143
const uint8_t aux_button_off[kHaierAC160StateLength] = {
0xA6, 0xAC, 0x00, 0x00, 0x40, 0x60, 0x00, 0x80, 0x00, 0x00,
0x00, 0x00, 0x16, 0x88, 0xB5, 0x00, 0x60, 0x00, 0x00, 0x15};
ac.setRaw(aux_button_off);
EXPECT_FALSE(ac.getAuxHeating());
EXPECT_EQ(kHaierAc160ButtonAuxHeating, ac.getButton());
EXPECT_EQ(
"Power: On, Button: 22 (Heating), Mode: 4 (Heat), Temp: 26C, "
"Fan: 3 (Low), Turbo: Off, Quiet: Off, Health: Off, Swing(V): 12 (Auto), "
"Sleep: Off, Clean: Off, Timer Mode: 0 (N/A), "
"On Timer: Off, Off Timer: Off, Lock: Off, Heating: Off",
ac.toString());
}