diff --git a/src/IRac.cpp b/src/IRac.cpp index 8f6676889..af6beb30b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -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); diff --git a/src/IRac.h b/src/IRac.h index 67f7bf768..9193a531d 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -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, diff --git a/src/ir_Haier.cpp b/src/ir_Haier.cpp index 93336212e..a60828282 100644 --- a/src/ir_Haier.cpp +++ b/src/ir_Haier.cpp @@ -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. diff --git a/src/ir_Haier.h b/src/ir_Haier.h index 9afc862a5..31cf4bcff 100644 --- a/src/ir_Haier.h +++ b/src/ir_Haier.h @@ -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_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 30d9f7e4f..15351ba61 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -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); diff --git a/test/ir_Haier_test.cpp b/test/ir_Haier_test.cpp index 57c813c0d..5f2b9a92d 100644 --- a/test/ir_Haier_test.cpp +++ b/test/ir_Haier_test.cpp @@ -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()); }