From 2d24333a9fa14083c4de81363855340d04ce31aa Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Fri, 23 Jul 2021 23:44:12 +1000 Subject: [PATCH 1/4] HAIER_AC176: Add experimental detailed support * `IRHaierACYRW02` inherits from the new `IRHaierAC176`. - Except for a few functions that need to be special. * Add it to the Common A/C API (`IRac`) * Expand & update the unit tests. Not fully happy with the support yet. Fixes #1480 --- src/IRac.cpp | 61 +++++++++++++++++++ src/IRac.h | 8 +++ src/ir_Haier.cpp | 135 +++++++++++++++++++++++++++-------------- src/ir_Haier.h | 111 ++++++++++++++++++++++++++++----- test/IRac_test.cpp | 27 +++++++++ test/ir_Haier_test.cpp | 36 +++++++++-- 6 files changed, 316 insertions(+), 62 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 8d3a0ecaf..9b4e10cbd 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -203,6 +203,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_HAIER_AC case decode_type_t::HAIER_AC: #endif +#if SEND_HAIER_AC176 + case decode_type_t::HAIER_AC176: +#endif // SEND_HAIER_AC176 #if SEND_HAIER_AC_YRW02 case decode_type_t::HAIER_AC_YRW02: #endif @@ -1083,6 +1086,40 @@ void IRac::haier(IRHaierAC *ac, } #endif // SEND_HAIER_AC +#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. +/// @param[in] on The power setting. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] swingv The vertical swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] filter Turn on the (ion/pollen/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. +void IRac::haier176(IRHaierAC176 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const bool turbo, + const bool filter, const int16_t sleep) { + ac->begin(); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwing(ac->convertSwingV(swingv)); + // No Horizontal Swing setting available. + // No Quiet setting available. + ac->setTurbo(turbo); + // No Light setting available. + ac->setHealth(filter); + // 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); + ac->send(); +} +#endif // SEND_HAIER_AC176 + #if SEND_HAIER_AC_YRW02 /// Send a Haier YRWO2 A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRHaierACYRW02 object to use. @@ -2655,6 +2692,15 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_HAIER_AC +#if SEND_HAIER_AC176 + case HAIER_AC176: + { + IRHaierAC176 ac(_pin, _inverted, _modulation); + haier176(&ac, send.power, send.mode, degC, send.fanspeed, send.swingv, + send.turbo, send.filter, send.sleep); + break; + } +#endif // SEND_HAIER_AC176 #if SEND_HAIER_AC_YRW02 case HAIER_AC_YRW02: { @@ -3558,6 +3604,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_HAIER_AC +#if DECODE_HAIER_AC176 + case decode_type_t::HAIER_AC176: { + IRHaierAC176 ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_HAIER_AC176 #if DECODE_HAIER_AC_YRW02 case decode_type_t::HAIER_AC_YRW02: { IRHaierACYRW02 ac(kGpioUnused); @@ -3907,6 +3960,14 @@ namespace IRAcUtils { break; } #endif // DECODE_HAIER_AC +#if DECODE_HAIER_AC176 + case decode_type_t::HAIER_AC176: { + IRHaierAC176 ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_HAIER_AC176 #if DECODE_HAIER_AC_YRW02 case decode_type_t::HAIER_AC_YRW02: { IRHaierACYRW02 ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index f7c3c6dc1..573e7ae7e 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -255,6 +255,14 @@ void electra(IRElectraAc *ac, const bool filter, const int16_t sleep = -1, const int16_t clock = -1); #endif // SEND_HAIER_AC +#if SEND_HAIER_AC176 + void haier176(IRHaierAC176 *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, + const bool turbo, const bool filter, + const int16_t sleep = -1); +#endif // SEND_HAIER_AC176 #if SEND_HAIER_AC_YRW02 void haierYrwo2(IRHaierACYRW02 *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/ir_Haier.cpp b/src/ir_Haier.cpp index 2a01f3b36..8e08e5961 100644 --- a/src/ir_Haier.cpp +++ b/src/ir_Haier.cpp @@ -537,63 +537,74 @@ String IRHaierAC::toString(void) const { /// @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? -IRHaierACYRW02::IRHaierACYRW02(const uint16_t pin, const bool inverted, - const bool use_modulation) +IRHaierAC176::IRHaierAC176(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 IRHaierACYRW02::begin(void) { _irsend.begin(); } +void IRHaierAC176::begin(void) { _irsend.begin(); } -#if SEND_HAIER_AC_YRW02 +#if SEND_HAIER_AC176 /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. -void IRHaierACYRW02::send(const uint16_t repeat) { - _irsend.sendHaierACYRW02(getRaw(), kHaierACYRW02StateLength, repeat); +void IRHaierAC176::send(const uint16_t repeat) { + _irsend.sendHaierAC176(getRaw(), kHaierAC176StateLength, repeat); } -#endif // SEND_HAIER_AC_YRW02 +#endif // SEND_HAIER_AC176 /// Calculate and set the checksum values for the internal state. -void IRHaierACYRW02::checksum(void) { +void IRHaierAC176::checksum(void) { _.Sum = sumBytes(_.raw, kHaierACYRW02StateLength - 1); + _.Sum2 = sumBytes(_.raw + kHaierACYRW02StateLength, + kHaierAC176StateLength - kHaierACYRW02StateLength - 1); } /// Verify the checksum is valid for a given state. /// @param[in] state The array to verify the checksum of. /// @param[in] length The length of the state array. /// @return true, if the state has a valid checksum. Otherwise, false. -bool IRHaierACYRW02::validChecksum(uint8_t state[], const uint16_t length) { +bool IRHaierAC176::validChecksum(const uint8_t state[], const uint16_t length) { if (length < 2) return false; // 1 byte of data can't have a checksum. - return (state[length - 1] == sumBytes(state, length - 1)); + if (length < kHaierAC176StateLength) { // Is it too short? + // Then it is just a checksum of the whole thing. + return (state[length - 1] == sumBytes(state, length - 1)); + } else { // It is long enough for two checksums. + return (state[kHaierACYRW02StateLength - 1] == + sumBytes(state, kHaierACYRW02StateLength - 1)) && + (state[length - 1] == + sumBytes(state + kHaierACYRW02StateLength, + length - kHaierACYRW02StateLength - 1)); + } } /// Reset the internal state to a fixed known good state. -void IRHaierACYRW02::stateReset(void) { +void IRHaierAC176::stateReset(void) { std::memset(_.raw, 0, sizeof _.raw); - _.Prefix = kHaierAcYrw02Prefix; + _.Prefix2 = kHaierAc176Prefix; _.Temp = kHaierAcDefTemp - kHaierAcMinTemp; _.Health = true; - _.Fan = kHaierAcYrw02FanAuto; + 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* IRHaierACYRW02::getRaw(void) { +uint8_t* IRHaierAC176::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 IRHaierACYRW02::setRaw(const uint8_t new_code[]) { - memcpy(_.raw, new_code, kHaierACYRW02StateLength); +void IRHaierAC176::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kHaierAC176StateLength); } /// Set the Button/Command setting of the A/C. /// @param[in] button The value of the button/command that was pressed. -void IRHaierACYRW02::setButton(uint8_t button) { +void IRHaierAC176::setButton(uint8_t button) { switch (button) { case kHaierAcYrw02ButtonTempUp: case kHaierAcYrw02ButtonTempDown: @@ -610,13 +621,13 @@ void IRHaierACYRW02::setButton(uint8_t button) { /// Get the Button/Command setting of the A/C. /// @return The value of the button/command that was pressed. -uint8_t IRHaierACYRW02::getButton(void) const { +uint8_t IRHaierAC176::getButton(void) const { return _.Button; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. -void IRHaierACYRW02::setMode(uint8_t mode) { +void IRHaierAC176::setMode(uint8_t mode) { uint8_t new_mode = mode; _.Button = kHaierAcYrw02ButtonMode; switch (mode) { @@ -632,13 +643,13 @@ void IRHaierACYRW02::setMode(uint8_t mode) { /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. -uint8_t IRHaierACYRW02::getMode(void) const { +uint8_t IRHaierAC176::getMode(void) const { return _.Mode; } /// Set the temperature. /// @param[in] celsius The temperature in degrees celsius. -void IRHaierACYRW02::setTemp(const uint8_t celsius) { +void IRHaierAC176::setTemp(const uint8_t celsius) { uint8_t temp = celsius; if (temp < kHaierAcMinTemp) temp = kHaierAcMinTemp; @@ -656,58 +667,58 @@ void IRHaierACYRW02::setTemp(const uint8_t celsius) { /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. -uint8_t IRHaierACYRW02::getTemp(void) const { +uint8_t IRHaierAC176::getTemp(void) const { return _.Temp + kHaierAcMinTemp; } /// Set the Health (filter) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRHaierACYRW02::setHealth(const bool on) { +void IRHaierAC176::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 IRHaierACYRW02::getHealth(void) const { +bool IRHaierAC176::getHealth(void) const { return _.Health; } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. -bool IRHaierACYRW02::getPower(void) const { +bool IRHaierAC176::getPower(void) const { return _.Power; } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRHaierACYRW02::setPower(const bool on) { +void IRHaierAC176::setPower(const bool on) { _.Button = kHaierAcYrw02ButtonPower; _.Power = on; } /// Change the power setting to On. -void IRHaierACYRW02::on(void) { setPower(true); } +void IRHaierAC176::on(void) { setPower(true); } /// Change the power setting to Off. -void IRHaierACYRW02::off(void) { setPower(false); } +void IRHaierAC176::off(void) { setPower(false); } /// Get the Sleep setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRHaierACYRW02::getSleep(void) const { +bool IRHaierAC176::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 IRHaierACYRW02::setSleep(const bool on) { +void IRHaierAC176::setSleep(const bool on) { _.Button = kHaierAcYrw02ButtonSleep; _.Sleep = on; } /// Get the Turbo setting of the A/C. /// @return The current turbo speed setting. -uint8_t IRHaierACYRW02::getTurbo(void) const { +uint8_t IRHaierAC176::getTurbo(void) const { return _.Turbo; } @@ -715,7 +726,7 @@ uint8_t IRHaierACYRW02::getTurbo(void) const { /// @param[in] speed The desired turbo speed setting. /// @note Valid speeds are kHaierAcYrw02TurboOff, kHaierAcYrw02TurboLow, & /// kHaierAcYrw02TurboHigh. -void IRHaierACYRW02::setTurbo(uint8_t speed) { +void IRHaierAC176::setTurbo(uint8_t speed) { switch (speed) { case kHaierAcYrw02TurboOff: case kHaierAcYrw02TurboLow: @@ -727,32 +738,33 @@ void IRHaierACYRW02::setTurbo(uint8_t speed) { /// Get the current fan speed setting. /// @return The current fan speed. -uint8_t IRHaierACYRW02::getFan(void) const { +uint8_t IRHaierAC176::getFan(void) const { return _.Fan; } /// Set the speed of the fan. /// @param[in] speed The desired setting. -void IRHaierACYRW02::setFan(uint8_t speed) { +void IRHaierAC176::setFan(uint8_t speed) { switch (speed) { case kHaierAcYrw02FanLow: case kHaierAcYrw02FanMed: case kHaierAcYrw02FanHigh: case kHaierAcYrw02FanAuto: _.Fan = speed; + _.Fan2 = (speed == kHaierAcYrw02FanAuto) ? 0 : speed; _.Button = kHaierAcYrw02ButtonFan; } } /// Get the Vertical Swing position setting of the A/C. /// @return The native position/mode. -uint8_t IRHaierACYRW02::getSwing(void) const { +uint8_t IRHaierAC176::getSwing(void) const { return _.Swing; } /// Set the Vertical Swing mode of the A/C. /// @param[in] pos The position/mode to set the vanes to. -void IRHaierACYRW02::setSwing(uint8_t pos) { +void IRHaierAC176::setSwing(uint8_t pos) { uint8_t newpos = pos; switch (pos) { case kHaierAcYrw02SwingOff: @@ -775,7 +787,7 @@ void IRHaierACYRW02::setSwing(uint8_t pos) { /// 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 IRHaierACYRW02::convertMode(const stdAc::opmode_t mode) { +uint8_t IRHaierAC176::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kHaierAcYrw02Cool; case stdAc::opmode_t::kHeat: return kHaierAcYrw02Heat; @@ -788,7 +800,7 @@ uint8_t IRHaierACYRW02::convertMode(const stdAc::opmode_t mode) { /// 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 IRHaierACYRW02::convertFan(const stdAc::fanspeed_t speed) { +uint8_t IRHaierAC176::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: case stdAc::fanspeed_t::kLow: return kHaierAcYrw02FanLow; @@ -802,7 +814,7 @@ uint8_t IRHaierACYRW02::convertFan(const stdAc::fanspeed_t speed) { /// 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 IRHaierACYRW02::convertSwingV(const stdAc::swingv_t position) { +uint8_t IRHaierAC176::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: case stdAc::swingv_t::kHigh: return kHaierAcYrw02SwingTop; @@ -817,7 +829,7 @@ uint8_t IRHaierACYRW02::convertSwingV(const stdAc::swingv_t position) { /// 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 IRHaierACYRW02::toCommonMode(const uint8_t mode) { +stdAc::opmode_t IRHaierAC176::toCommonMode(const uint8_t mode) { switch (mode) { case kHaierAcYrw02Cool: return stdAc::opmode_t::kCool; case kHaierAcYrw02Heat: return stdAc::opmode_t::kHeat; @@ -830,7 +842,7 @@ stdAc::opmode_t IRHaierACYRW02::toCommonMode(const uint8_t mode) { /// 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 IRHaierACYRW02::toCommonFanSpeed(const uint8_t speed) { +stdAc::fanspeed_t IRHaierAC176::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kHaierAcYrw02FanHigh: return stdAc::fanspeed_t::kMax; case kHaierAcYrw02FanMed: return stdAc::fanspeed_t::kMedium; @@ -842,7 +854,7 @@ stdAc::fanspeed_t IRHaierACYRW02::toCommonFanSpeed(const uint8_t speed) { /// 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 IRHaierACYRW02::toCommonSwingV(const uint8_t pos) { +stdAc::swingv_t IRHaierAC176::toCommonSwingV(const uint8_t pos) { switch (pos) { case kHaierAcYrw02SwingTop: return stdAc::swingv_t::kHighest; case kHaierAcYrw02SwingMiddle: return stdAc::swingv_t::kMiddle; @@ -855,7 +867,7 @@ stdAc::swingv_t IRHaierACYRW02::toCommonSwingV(const uint8_t pos) { /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. -stdAc::state_t IRHaierACYRW02::toCommon(void) const { +stdAc::state_t IRHaierAC176::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::HAIER_AC_YRW02; result.model = -1; // No models used. @@ -881,7 +893,7 @@ stdAc::state_t IRHaierACYRW02::toCommon(void) const { /// Convert the current internal state into a human readable string. /// @return A human readable string. -String IRHaierACYRW02::toString(void) const { +String IRHaierAC176::toString(void) const { String result = ""; result.reserve(130); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); @@ -972,8 +984,42 @@ String IRHaierACYRW02::toString(void) const { result += addBoolToString(_.Health, kHealthStr); return result; } +// End of IRHaierAC176 class. + + +/// 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? +IRHaierACYRW02::IRHaierACYRW02(const uint16_t pin, const bool inverted, + const bool use_modulation) + : IRHaierAC176(pin, inverted, use_modulation) { stateReset(); } + +#if SEND_HAIER_AC_YRW02 +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRHaierACYRW02::send(const uint16_t repeat) { + _irsend.sendHaierACYRW02(getRaw(), kHaierACYRW02StateLength, repeat); +} +#endif // SEND_HAIER_AC_YRW02 + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRHaierACYRW02::setRaw(const uint8_t new_code[]) { + memcpy(_.raw, new_code, kHaierACYRW02StateLength); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRHaierACYRW02::validChecksum(const uint8_t state[], + const uint16_t length) { + return IRHaierAC176::validChecksum(state, length); +} // End of IRHaierACYRW02 class. + #if (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02) /// Decode the supplied Haier HSU07-HEA03 remote message. /// Status: STABLE / Known to be working. @@ -1076,6 +1122,7 @@ bool IRrecv::decodeHaierAC176(decode_results* results, uint16_t offset, // Compliance if (strict) { if (results->state[0] != kHaierAcYrw02Prefix) return false; + if (!IRHaierAC176::validChecksum(results->state, nbits / 8)) return false; } // Success diff --git a/src/ir_Haier.h b/src/ir_Haier.h index df696642c..8e9c3b199 100644 --- a/src/ir_Haier.h +++ b/src/ir_Haier.h @@ -179,6 +179,7 @@ union HaierYRW02Protocol{ }; const uint8_t kHaierAcYrw02Prefix = 0xA6; +const uint8_t kHaierAc176Prefix = 0xB7; const uint8_t kHaierAcYrw02SwingOff = 0x0; const uint8_t kHaierAcYrw02SwingTop = 0x1; @@ -190,7 +191,7 @@ const uint8_t kHaierAcYrw02SwingAuto = 0xC; // Airflow const uint8_t kHaierAcYrw02FanHigh = 0b001; const uint8_t kHaierAcYrw02FanMed = 0b010; const uint8_t kHaierAcYrw02FanLow = 0b011; -const uint8_t kHaierAcYrw02FanAuto = 0b101; +const uint8_t kHaierAcYrw02FanAuto = 0b101; // HAIER_AC176 uses `0` in Fan2 const uint8_t kHaierAcYrw02TurboOff = 0x0; const uint8_t kHaierAcYrw02TurboHigh = 0x1; @@ -212,6 +213,68 @@ const uint8_t kHaierAcYrw02ButtonHealth = 0x7; const uint8_t kHaierAcYrw02ButtonTurbo = 0x8; const uint8_t kHaierAcYrw02ButtonSleep = 0xB; +/// Native representation of a Haier 176 bit A/C message. +union HaierAc176Protocol{ + uint8_t raw[kHaierAC176StateLength]; ///< The state in native form + struct { + // Byte 0 + uint8_t Prefix; + // Byte 1 + uint8_t Swing:4; + uint8_t Temp :4; // 16C~30C + // Byte 2 + uint8_t :8; + // Byte 3 + uint8_t :1; + uint8_t Health:1; + uint8_t :6; + // Byte 4 + uint8_t :6; + uint8_t Power:1; + uint8_t :1; + // Byte 5 + uint8_t :5; + uint8_t Fan:3; + // Byte 6 + uint8_t :6; + uint8_t Turbo:2; + // Byte 7 + uint8_t :5; + uint8_t Mode:3; + // Byte 8 + uint8_t :7; + uint8_t Sleep:1; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t Button:4; + uint8_t :4; + // Byte 13 + uint8_t Sum; + // Byte 14 + uint8_t Prefix2; + // Byte 15 + uint8_t :8; + // Byte 16 + uint8_t :6; + uint8_t Fan2:2; + // Byte 17 + uint8_t :8; + // Byte 18 + uint8_t :8; + // Byte 19 + uint8_t :8; + // Byte 20 + uint8_t :8; + // Byte 21 + uint8_t Sum2:8; + }; +}; + // Legacy Haier YRW02 remote defines. #define HAIER_AC_YRW02_SWING_OFF kHaierAcYrw02SwingOff #define HAIER_AC_YRW02_SWING_TOP kHaierAcYrw02SwingTop @@ -256,6 +319,7 @@ class IRHaierAC { int8_t calibrate(void) { return _irsend.calibrate(); } #endif // SEND_HAIER_AC void begin(void); + void stateReset(void); void setCommand(const uint8_t command); uint8_t getCommand(void) const; @@ -308,24 +372,25 @@ class IRHaierAC { /// @endcond #endif HaierProtocol _; - void stateReset(void); void checksum(void); }; -/// Class for handling detailed Haier ACYRW02 A/C messages. -class IRHaierACYRW02 { +/// Class for handling detailed Haier 176 bit A/C messages. +class IRHaierAC176 { + friend class IRHaierACYRW02; public: - explicit IRHaierACYRW02(const uint16_t pin, const bool inverted = false, - const bool use_modulation = true); -#if SEND_HAIER_AC_YRW02 - void send(const uint16_t repeat = kHaierAcYrw02DefaultRepeat); + explicit IRHaierAC176(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC176 + virtual void send(const uint16_t repeat = kHaierAc176DefaultRepeat); /// 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_AC_YRW02 +#endif // SEND_HAIER_AC176 void begin(void); + void stateReset(void); void setButton(const uint8_t button); uint8_t getButton(void) const; @@ -356,9 +421,9 @@ class IRHaierACYRW02 { void setSwing(const uint8_t pos); uint8_t* getRaw(void); - void setRaw(const uint8_t new_code[]); - static bool validChecksum(uint8_t state[], - const uint16_t length = kHaierACYRW02StateLength); + virtual void setRaw(const uint8_t new_code[]); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kHaierAC176StateLength); 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); @@ -376,8 +441,26 @@ class IRHaierACYRW02 { IRsendTest _irsend; ///< Instance of the testing IR send class /// @endcond #endif // UNIT_TEST - HaierYRW02Protocol _; - void stateReset(void); + HaierAc176Protocol _; void checksum(void); }; + +/// Class for handling detailed Haier ACYRW02 A/C messages. +class IRHaierACYRW02 : public IRHaierAC176 { + public: + explicit IRHaierACYRW02(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); +#if SEND_HAIER_AC_YRW02 + void send(const uint16_t repeat = kHaierAcYrw02DefaultRepeat) override; + /// 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_AC_YRW02 + void setRaw(const uint8_t new_code[]) override; + static bool validChecksum( + const uint8_t state[], + const uint16_t length = kHaierACYRW02StateLength); +}; #endif // IR_HAIER_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 5f07dd75c..96b5ff035 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -783,6 +783,33 @@ TEST(TestIRac, Haier) { ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); } +TEST(TestIRac, Haier176) { + IRHaierAC176 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: 1 (High), Swing: 1 (Highest), Sleep: On, " + "Health: On"; + ac.begin(); + irac.haier176(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 23, // Celsius + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kHigh, // Vertical swing + true, // Turbo + true, // Filter + 8 * 60 + 0); // Sleep time + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(HAIER_AC176, ac._irsend.capture.decode_type); + ASSERT_EQ(kHaierAC176Bits, 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, HaierYrwo2) { IRHaierACYRW02 ac(kGpioUnused); diff --git a/test/ir_Haier_test.cpp b/test/ir_Haier_test.cpp index 77b475f6e..56649633b 100644 --- a/test/ir_Haier_test.cpp +++ b/test/ir_Haier_test.cpp @@ -988,7 +988,7 @@ TEST(TestDecodeHaierAC_YRW02, RealExample) { // Default state of the remote needed to include hidden data. // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/668 TEST(TestHaierAcIssues, Issue668) { - IRHaierAC ac(0); + IRHaierAC ac(kGpioUnused); IRHaierAC acText(1); IRrecv irrecv(kGpioUnused); ac.begin(); @@ -1075,7 +1075,7 @@ TEST(TestHaierAcIssues, Issue668) { } TEST(TestHaierACClass, toCommon) { - IRHaierAC ac(0); + IRHaierAC ac(kGpioUnused); ac.setCommand(kHaierAcCmdOn); ac.setMode(kHaierAcCool); ac.setTemp(20); @@ -1106,7 +1106,7 @@ TEST(TestHaierACClass, toCommon) { } TEST(TestHaierACYRW02Class, toCommon) { - IRHaierACYRW02 ac(0); + IRHaierACYRW02 ac(kGpioUnused); ac.setPower(true); ac.setMode(kHaierAcYrw02Cool); ac.setTemp(20); @@ -1202,6 +1202,12 @@ TEST(TestDecodeHaierAC176, SyntheticDecode) { EXPECT_EQ(kHaierAC176Bits, irsend.capture.bits); 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: 24C, Fan: 5 (Auto), " + "Turbo: 0 (Off), Swing: 6 (UNKNOWN), Sleep: Off, Health: Off", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t result, prev; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); } TEST(TestUtils, Housekeeping) { @@ -1223,7 +1229,29 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("HAIER_AC176", typeToString(decode_type_t::HAIER_AC176)); ASSERT_EQ(decode_type_t::HAIER_AC176, strToDecodeType("HAIER_AC176")); ASSERT_TRUE(hasACState(decode_type_t::HAIER_AC176)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::HAIER_AC176)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::HAIER_AC176)); ASSERT_EQ(kHaierAC176Bits, IRsend::defaultBits(decode_type_t::HAIER_AC176)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::HAIER_AC176)); } + +TEST(TestHaierAC176Class, BuildKnownState) { + IRHaierAC176 ac(kGpioUnused); + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1480#issuecomment-884920033 + // heat, 24 C, fan max + ac.setTemp(24); + ac.setMode(kHaierAcYrw02Heat); + ac.setFan(kHaierAcYrw02FanHigh); + EXPECT_TRUE(ac.validChecksum(ac.getRaw())); + EXPECT_EQ( + "Power: On, Button: 4 (Fan), Mode: 4 (Heat), Temp: 24C, Fan: 1 (High), " + "Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, Health: On", + ac.toString()); + /* Disabled pending: + https://github.com/crankyoldgit/IRremoteESP8266/issues/1480#issuecomment-885636790 + const uint8_t expectedState[kHaierAC176StateLength] = { + 0xA6, 0x0A, 0x00, 0x00, 0x40, 0x20, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x04, 0x94, + 0xB7, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0xF7}; + EXPECT_STATE_EQ(expectedState, ac.getRaw(), kHaierAC176StateLength); + */ +} From 9a0367ab7d1cf95c1777ae1c628641140d1eb26a Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 21 Aug 2021 18:14:32 +1000 Subject: [PATCH 2/4] Haier176/YRW02: Add On/Off Timer support. * Add `[sg]et(On|Off)Timer()` class methods. * Update `toString()`. * Add timer specific unit tests. * Update existing Unit Tests. For #1480 --- src/ir_Haier.cpp | 41 ++++++++++++- src/ir_Haier.h | 29 ++++++--- test/IRac_test.cpp | 4 +- test/ir_Haier_test.cpp | 135 ++++++++++++++++++++++++++++++++++------- 4 files changed, 171 insertions(+), 38 deletions(-) diff --git a/src/ir_Haier.cpp b/src/ir_Haier.cpp index 8e08e5961..c67723e99 100644 --- a/src/ir_Haier.cpp +++ b/src/ir_Haier.cpp @@ -11,6 +11,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1480 #include "ir_Haier.h" +#include #include #ifndef UNIT_TEST #include @@ -758,9 +759,7 @@ void IRHaierAC176::setFan(uint8_t speed) { /// Get the Vertical Swing position setting of the A/C. /// @return The native position/mode. -uint8_t IRHaierAC176::getSwing(void) const { - return _.Swing; -} +uint8_t IRHaierAC176::getSwing(void) const { return _.Swing; } /// Set the Vertical Swing mode of the A/C. /// @param[in] pos The position/mode to set the vanes to. @@ -784,6 +783,36 @@ void IRHaierAC176::setSwing(uint8_t pos) { _.Swing = newpos; } +/// 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 IRHaierAC176::setOnTimer(const uint16_t mins) { + const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins); + _.OnTimerSet = nr_mins > 0; + _.OnTimerHrs = nr_mins / 60; + _.OnTimerMins = nr_mins % 60; +} + +/// Get the number of minutes of the On Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC176::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 IRHaierAC176::setOffTimer(const uint16_t mins) { + const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins); + _.OffTimerSet = nr_mins > 0; + _.OffTimerHrs = nr_mins / 60; + _.OffTimerMins = nr_mins % 60; +} + +/// Get the number of minutes of the Off Timer setting. +/// @return Nr of minutes. +uint16_t IRHaierAC176::getOffTimer(void) const { + return _.OffTimerHrs * 60 + _.OffTimerMins; +} + /// 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. @@ -982,6 +1011,12 @@ String IRHaierAC176::toString(void) const { result += ')'; result += addBoolToString(_.Sleep, kSleepStr); result += addBoolToString(_.Health, kHealthStr); + result += addLabeledString(_.OnTimerSet ? minsToString(getOnTimer()) + : kOffStr, + kOnTimerStr); + result += addLabeledString(_.OffTimerSet ? minsToString(getOffTimer()) + : kOffStr, + kOffTimerStr); return result; } // End of IRHaierAC176 class. diff --git a/src/ir_Haier.h b/src/ir_Haier.h index 8e9c3b199..9a5d5573c 100644 --- a/src/ir_Haier.h +++ b/src/ir_Haier.h @@ -225,25 +225,29 @@ union HaierAc176Protocol{ // Byte 2 uint8_t :8; // Byte 3 - uint8_t :1; - uint8_t Health:1; - uint8_t :6; + uint8_t :1; + uint8_t Health :1; + uint8_t :3; + uint8_t OffTimerSet :1; + uint8_t OnTimerSet :1; + uint8_t :1; // Byte 4 uint8_t :6; uint8_t Power:1; uint8_t :1; // Byte 5 - uint8_t :5; - uint8_t Fan:3; + uint8_t OffTimerHrs :5; + uint8_t Fan :3; // Byte 6 - uint8_t :6; + uint8_t OffTimerMins:6; uint8_t Turbo:2; // Byte 7 - uint8_t :5; - uint8_t Mode:3; + uint8_t OnTimerHrs :5; + uint8_t Mode :3; // Byte 8 - uint8_t :7; - uint8_t Sleep:1; + uint8_t OnTimerMins :6; + uint8_t :1; + uint8_t Sleep :1; // Byte 9 uint8_t :8; // Byte 10 @@ -420,6 +424,11 @@ class IRHaierAC176 { uint8_t getSwing(void) const; void setSwing(const uint8_t pos); + void setOnTimer(const uint16_t mins); + uint16_t getOnTimer(void) const; + void setOffTimer(const uint16_t mins); + uint16_t getOffTimer(void) const; + uint8_t* getRaw(void); virtual void setRaw(const uint8_t new_code[]); static bool validChecksum(const uint8_t state[], diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 96b5ff035..998b971b1 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -790,7 +790,7 @@ TEST(TestIRac, Haier176) { const char expected[] = "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 23C, " "Fan: 2 (Medium), Turbo: 1 (High), Swing: 1 (Highest), Sleep: On, " - "Health: On"; + "Health: On, On Timer: Off, Off Timer: Off"; ac.begin(); irac.haier176(&ac, true, // Power @@ -818,7 +818,7 @@ TEST(TestIRac, HaierYrwo2) { char expected[] = "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 23C, " "Fan: 2 (Medium), Turbo: 1 (High), Swing: 1 (Highest), Sleep: On, " - "Health: On"; + "Health: On, On Timer: Off, Off Timer: Off"; ac.begin(); irac.haierYrwo2(&ac, diff --git a/test/ir_Haier_test.cpp b/test/ir_Haier_test.cpp index 56649633b..c56d5f633 100644 --- a/test/ir_Haier_test.cpp +++ b/test/ir_Haier_test.cpp @@ -675,7 +675,7 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { EXPECT_EQ( "Power: On, Button: 5 (Power), Mode: 0 (Auto), Temp: 25C," " Fan: 5 (Auto), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off," - " Health: On", + " Health: On, On Timer: Off, Off Timer: Off", haier.toString()); haier.setMode(kHaierAcYrw02Cool); haier.setTemp(21); @@ -683,7 +683,7 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { EXPECT_EQ( "Power: On, Button: 4 (Fan), Mode: 1 (Cool), Temp: 21C," " Fan: 1 (High), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off," - " Health: On", + " Health: On, On Timer: Off, Off Timer: Off", haier.toString()); haier.setSwing(kHaierAcYrw02SwingMiddle); @@ -693,7 +693,7 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { EXPECT_EQ( "Power: On, Button: 8 (Turbo), Mode: 1 (Cool), Temp: 21C," " Fan: 1 (High), Turbo: 1 (High), Swing: 2 (Middle)," - " Sleep: On, Health: Off", + " Sleep: On, Health: Off, On Timer: Off, Off Timer: Off", haier.toString()); } @@ -706,9 +706,9 @@ TEST(TestHaierACYRW02Class, RealStates) { IRHaierACYRW02 haier(kGpioUnused); haier.setRaw(expectedState1); EXPECT_EQ( - "Power: On, Button: 7 (Health), Mode: 4 (Heat), Temp: 30C," - " Fan: 1 (High), Turbo: 0 (Off), Swing: 1 (Highest), Sleep: Off," - " Health: Off", + "Power: On, Button: 7 (Health), Mode: 4 (Heat), Temp: 30C, " + "Fan: 1 (High), Turbo: 0 (Off), Swing: 1 (Highest), Sleep: Off, " + "Health: Off, On Timer: Off, Off Timer: Off", haier.toString()); uint8_t expectedState2[kHaierACYRW02StateLength] = { @@ -716,9 +716,9 @@ TEST(TestHaierACYRW02Class, RealStates) { 0x80, 0x00, 0x00, 0x00, 0x00, 0x05, 0x75}; haier.setRaw(expectedState2); EXPECT_EQ( - "Power: Off, Button: 5 (Power), Mode: 4 (Heat), Temp: 30C," - " Fan: 1 (High), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off," - " Health: Off", + "Power: Off, Button: 5 (Power), Mode: 4 (Heat), Temp: 30C, " + "Fan: 1 (High), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, " + "Health: Off, On Timer: Off, Off Timer: Off", haier.toString()); uint8_t expectedState3[kHaierACYRW02StateLength] = { @@ -726,9 +726,9 @@ TEST(TestHaierACYRW02Class, RealStates) { 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2B}; haier.setRaw(expectedState3); EXPECT_EQ( - "Power: On, Button: 1 (Temp Down), Mode: 1 (Cool), Temp: 16C," - " Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off," - " Health: On", + "Power: On, Button: 1 (Temp Down), Mode: 1 (Cool), Temp: 16C, " + "Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: On, On Timer: Off, Off Timer: Off", haier.toString()); // cool 25, health, fan auto, swing auto, sleep on @@ -737,9 +737,9 @@ TEST(TestHaierACYRW02Class, RealStates) { 0x20, 0x80, 0x00, 0x00, 0x00, 0x0B, 0xD7}; haier.setRaw(expectedState4); EXPECT_EQ( - "Power: On, Button: 11 (Sleep), Mode: 1 (Cool), Temp: 25C," - " Fan: 5 (Auto), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On," - " Health: On", + "Power: On, Button: 11 (Sleep), Mode: 1 (Cool), Temp: 25C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On, " + "Health: On, On Timer: Off, Off Timer: Off", haier.toString()); // cool 25, health, fan 3, swing auto, sleep on @@ -748,9 +748,9 @@ TEST(TestHaierACYRW02Class, RealStates) { 0x20, 0x80, 0x00, 0x00, 0x00, 0x04, 0x85}; haier.setRaw(expectedState5); EXPECT_EQ( - "Power: On, Button: 4 (Fan), Mode: 1 (Cool), Temp: 25C," - " Fan: 1 (High), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On," - " Health: On", + "Power: On, Button: 4 (Fan), Mode: 1 (Cool), Temp: 25C, " + "Fan: 1 (High), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On, " + "Health: On, On Timer: Off, Off Timer: Off", haier.toString()); } @@ -979,9 +979,9 @@ TEST(TestDecodeHaierAC_YRW02, RealExample) { IRHaierACYRW02 haier(kGpioUnused); haier.setRaw(irsend.capture.state); EXPECT_EQ( - "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 17C," - " Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off," - " Health: On", + "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 17C, " + "Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: On, On Timer: Off, Off Timer: Off", haier.toString()); } @@ -1204,7 +1204,8 @@ TEST(TestDecodeHaierAC176, SyntheticDecode) { EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 24C, Fan: 5 (Auto), " - "Turbo: 0 (Off), Swing: 6 (UNKNOWN), Sleep: Off, Health: Off", + "Turbo: 0 (Off), Swing: 6 (UNKNOWN), Sleep: Off, Health: Off, " + "On Timer: Off, Off Timer: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); @@ -1244,7 +1245,8 @@ TEST(TestHaierAC176Class, BuildKnownState) { EXPECT_TRUE(ac.validChecksum(ac.getRaw())); EXPECT_EQ( "Power: On, Button: 4 (Fan), Mode: 4 (Heat), Temp: 24C, Fan: 1 (High), " - "Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, Health: On", + "Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, Health: On, " + "On Timer: Off, Off Timer: Off", ac.toString()); /* Disabled pending: https://github.com/crankyoldgit/IRremoteESP8266/issues/1480#issuecomment-885636790 @@ -1255,3 +1257,90 @@ TEST(TestHaierAC176Class, BuildKnownState) { EXPECT_STATE_EQ(expectedState, ac.getRaw(), kHaierAC176StateLength); */ } + +TEST(TestHaierAC176Class, Timers) { + IRHaierAC176 ac(kGpioUnused); + EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_FALSE(ac._.OffTimerSet); + EXPECT_EQ(0, ac.getOffTimer()); + + // On Timer + ac.setOnTimer(1); + EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(1, ac.getOnTimer()); + + ac.setOnTimer(0); + EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOnTimer(1 * 60); + EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(1 * 60, ac.getOnTimer()); + + ac.setOnTimer(2 * 60 + 37); + EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(2 * 60 + 37, ac.getOnTimer()); + + ac.setOnTimer(23 * 60 + 59); // Max + EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(23 * 60 + 59, ac.getOnTimer()); + + ac.setOnTimer(24 * 60); // Beyond max + EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(23 * 60 + 59, ac.getOnTimer()); // Max + + ac.setOnTimer(0); + EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(0, ac.getOnTimer()); + + // Off Timer + ac.setOffTimer(1); + EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(1, ac.getOffTimer()); + + ac.setOffTimer(0); + EXPECT_FALSE(ac._.OffTimerSet); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOffTimer(1 * 60); + EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(1 * 60, ac.getOffTimer()); + + ac.setOffTimer(2 * 60 + 37); + EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(2 * 60 + 37, ac.getOffTimer()); + + ac.setOffTimer(23 * 60 + 59); // Max + EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(23 * 60 + 59, ac.getOffTimer()); + + ac.setOffTimer(24 * 60); // Beyond max + EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(23 * 60 + 59, ac.getOffTimer()); // Max + + ac.setOffTimer(0); + EXPECT_FALSE(ac._.OffTimerSet); + EXPECT_EQ(0, ac.getOffTimer()); + + // Real data. + // Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1480#issuecomment-894804106 + const uint8_t timer30m[22] = { + 0xA6, 0x82, 0x00, 0x40, 0x00, 0xA0, 0x00, 0x00, 0x1E, 0x00, 0x00, + 0x00, 0x10, 0x36, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7}; + const uint8_t timeroff[22] = { + 0xA6, 0x82, 0x00, 0x00, 0x40, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x18, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7}; + ac.setRaw(timer30m); + EXPECT_EQ( + "Power: Off, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, On Timer: 00:30, Off Timer: Off", + ac.toString()); + ac.setRaw(timeroff); + EXPECT_EQ( + "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, On Timer: Off, Off Timer: Off", + ac.toString()); +} From 392c32a90674a608ff439b5c58ab9064675ca14e Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Mon, 23 Aug 2021 15:39:21 +1000 Subject: [PATCH 3/4] HaierYrw02: Add Timer Mode support. Ref: https://docs.google.com/spreadsheets/d/1wdOVS08wgK2pEP7hTZLYMmrQ9FZVmLpZF2HjNQaVxlU/edit?disco=AAAAOCMm-Gg For #1480 --- src/IRtext.cpp | 5 ++- src/IRtext.h | 1 + src/ir_Haier.cpp | 97 +++++++++++++++++++++++++++++++++++++---- src/ir_Haier.h | 62 ++++++++++++++------------ src/locale/defaults.h | 3 ++ test/IRac_test.cpp | 4 +- test/ir_Haier_test.cpp | 98 ++++++++++++++++++++++++++++-------------- 7 files changed, 197 insertions(+), 73 deletions(-) diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 21b96b67f..486e57b0f 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -39,8 +39,9 @@ const PROGMEM char* kMouldStr = D_STR_MOULD; ///< "Mould" const PROGMEM char* kCleanStr = D_STR_CLEAN; ///< "Clean" const PROGMEM char* kPurifyStr = D_STR_PURIFY; ///< "Purify" const PROGMEM char* kTimerStr = D_STR_TIMER; ///< "Timer" -const PROGMEM char* kOnTimerStr = D_STR_ONTIMER; ///< "OnTimer" -const PROGMEM char* kOffTimerStr = D_STR_OFFTIMER; ///< "OffTimer" +const PROGMEM char* kOnTimerStr = D_STR_ONTIMER; ///< "On Timer" +const PROGMEM char* kOffTimerStr = D_STR_OFFTIMER; ///< "Off Timer" +const PROGMEM char* kTimerModeStr = D_STR_TIMERMODE; ///< "Timer Mode" const PROGMEM char* kClockStr = D_STR_CLOCK; ///< "Clock" const PROGMEM char* kCommandStr = D_STR_COMMAND; ///< "Command" const PROGMEM char* kXFanStr = D_STR_XFAN; ///< "XFan" diff --git a/src/IRtext.h b/src/IRtext.h index cb2ba5a9a..55840688d 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -146,6 +146,7 @@ extern const char* kTempDownStr; extern const char* kTempStr; extern const char* kTempUpStr; extern const char* kThreeLetterDayOfWeekStr; +extern const char* kTimerModeStr; extern const char* kTimerStr; extern const char* kToggleStr; extern const char* kTopStr; diff --git a/src/ir_Haier.cpp b/src/ir_Haier.cpp index c67723e99..03a2cd5e3 100644 --- a/src/ir_Haier.cpp +++ b/src/ir_Haier.cpp @@ -783,13 +783,52 @@ void IRHaierAC176::setSwing(uint8_t pos) { _.Swing = newpos; } + +/// Set the Timer operating mode. +/// @param[in] mode The timer mode to use. +void IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::setOnTimer(const uint16_t mins) { const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins); - _.OnTimerSet = nr_mins > 0; _.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. @@ -802,9 +841,24 @@ uint16_t IRHaierAC176::getOnTimer(void) const { /// @param[in] mins Nr. of Minutes for the Timer. `0` means disable the timer. void IRHaierAC176::setOffTimer(const uint16_t mins) { const uint16_t nr_mins = std::min((uint16_t)(23 * 60 + 59), mins); - _.OffTimerSet = nr_mins > 0; _.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. @@ -1011,12 +1065,39 @@ String IRHaierAC176::toString(void) const { result += ')'; result += addBoolToString(_.Sleep, kSleepStr); result += addBoolToString(_.Health, kHealthStr); - result += addLabeledString(_.OnTimerSet ? minsToString(getOnTimer()) - : kOffStr, - kOnTimerStr); - result += addLabeledString(_.OffTimerSet ? minsToString(getOffTimer()) - : kOffStr, - kOffTimerStr); + 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); return result; } // End of IRHaierAC176 class. diff --git a/src/ir_Haier.h b/src/ir_Haier.h index 9a5d5573c..2e287cd12 100644 --- a/src/ir_Haier.h +++ b/src/ir_Haier.h @@ -213,28 +213,32 @@ const uint8_t kHaierAcYrw02ButtonHealth = 0x7; const uint8_t kHaierAcYrw02ButtonTurbo = 0x8; const uint8_t kHaierAcYrw02ButtonSleep = 0xB; +const uint8_t kHaierAcYrw02NoTimers = 0b000; +const uint8_t kHaierAcYrw02OffTimer = 0b001; +const uint8_t kHaierAcYrw02OnTimer = 0b010; +const uint8_t kHaierAcYrw02OnThenOffTimer = 0b100; +const uint8_t kHaierAcYrw02OffThenOnTimer = 0b101; + /// Native representation of a Haier 176 bit A/C message. union HaierAc176Protocol{ uint8_t raw[kHaierAC176StateLength]; ///< The state in native form struct { // Byte 0 - uint8_t Prefix; + uint8_t Prefix :8; // Byte 1 - uint8_t Swing:4; - uint8_t Temp :4; // 16C~30C + uint8_t Swing :4; + uint8_t Temp :4; // 16C~30C // Byte 2 - uint8_t :8; + uint8_t :8; // Byte 3 - uint8_t :1; - uint8_t Health :1; - uint8_t :3; - uint8_t OffTimerSet :1; - uint8_t OnTimerSet :1; - uint8_t :1; + 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 :1; + uint8_t :6; + uint8_t Power :1; + uint8_t :1; // Byte 5 uint8_t OffTimerHrs :5; uint8_t Fan :3; @@ -249,33 +253,33 @@ union HaierAc176Protocol{ uint8_t :1; uint8_t Sleep :1; // Byte 9 - uint8_t :8; + uint8_t :8; // Byte 10 - uint8_t :8; + uint8_t :8; // Byte 11 - uint8_t :8; + uint8_t :8; // Byte 12 - uint8_t Button:4; - uint8_t :4; + uint8_t Button :4; + uint8_t :4; // Byte 13 - uint8_t Sum; + uint8_t Sum :8; // Byte 14 - uint8_t Prefix2; + uint8_t Prefix2 :8; // Byte 15 - uint8_t :8; + uint8_t :8; // Byte 16 - uint8_t :6; - uint8_t Fan2:2; + uint8_t :6; + uint8_t Fan2 :2; // Byte 17 - uint8_t :8; + uint8_t :8; // Byte 18 - uint8_t :8; + uint8_t :8; // Byte 19 - uint8_t :8; + uint8_t :8; // Byte 20 - uint8_t :8; + uint8_t :8; // Byte 21 - uint8_t Sum2:8; + uint8_t Sum2 :8; }; }; @@ -424,6 +428,8 @@ class IRHaierAC176 { uint8_t getSwing(void) const; void setSwing(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); diff --git a/src/locale/defaults.h b/src/locale/defaults.h index dd3108cb4..1e1896b87 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -90,6 +90,9 @@ #ifndef D_STR_OFFTIMER #define D_STR_OFFTIMER D_STR_OFF " " D_STR_TIMER // Set `D_STR_OFF` first! #endif // D_STR_OFFTIMER +#ifndef D_STR_TIMERMODE +#define D_STR_TIMERMODE D_STR_TIMER " " D_STR_MODE // Set `D_STR_MODE` first! +#endif // D_STR_TIMERMODE #ifndef D_STR_CLOCK #define D_STR_CLOCK "Clock" #endif // D_STR_CLOCK diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 998b971b1..140e50137 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -790,7 +790,7 @@ TEST(TestIRac, Haier176) { const char expected[] = "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 23C, " "Fan: 2 (Medium), Turbo: 1 (High), Swing: 1 (Highest), Sleep: On, " - "Health: On, On Timer: Off, Off Timer: Off"; + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off"; ac.begin(); irac.haier176(&ac, true, // Power @@ -818,7 +818,7 @@ TEST(TestIRac, HaierYrwo2) { char expected[] = "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 23C, " "Fan: 2 (Medium), Turbo: 1 (High), Swing: 1 (Highest), Sleep: On, " - "Health: On, On Timer: Off, Off Timer: Off"; + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off"; ac.begin(); irac.haierYrwo2(&ac, diff --git a/test/ir_Haier_test.cpp b/test/ir_Haier_test.cpp index c56d5f633..8b89b1098 100644 --- a/test/ir_Haier_test.cpp +++ b/test/ir_Haier_test.cpp @@ -675,7 +675,7 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { EXPECT_EQ( "Power: On, Button: 5 (Power), Mode: 0 (Auto), Temp: 25C," " Fan: 5 (Auto), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off," - " Health: On, On Timer: Off, Off Timer: Off", + " Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); haier.setMode(kHaierAcYrw02Cool); haier.setTemp(21); @@ -683,7 +683,7 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { EXPECT_EQ( "Power: On, Button: 4 (Fan), Mode: 1 (Cool), Temp: 21C," " Fan: 1 (High), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off," - " Health: On, On Timer: Off, Off Timer: Off", + " Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); haier.setSwing(kHaierAcYrw02SwingMiddle); @@ -691,9 +691,10 @@ TEST(TestHaierACYRW02Class, MessageConstuction) { haier.setSleep(true); haier.setTurbo(kHaierAcYrw02TurboHigh); EXPECT_EQ( - "Power: On, Button: 8 (Turbo), Mode: 1 (Cool), Temp: 21C," - " Fan: 1 (High), Turbo: 1 (High), Swing: 2 (Middle)," - " Sleep: On, Health: Off, On Timer: Off, Off Timer: Off", + "Power: On, Button: 8 (Turbo), Mode: 1 (Cool), Temp: 21C, " + "Fan: 1 (High), Turbo: 1 (High), Swing: 2 (Middle), " + "Sleep: On, Health: Off, " + "Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); } @@ -708,7 +709,7 @@ TEST(TestHaierACYRW02Class, RealStates) { EXPECT_EQ( "Power: On, Button: 7 (Health), Mode: 4 (Heat), Temp: 30C, " "Fan: 1 (High), Turbo: 0 (Off), Swing: 1 (Highest), Sleep: Off, " - "Health: Off, On Timer: Off, Off Timer: Off", + "Health: Off, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); uint8_t expectedState2[kHaierACYRW02StateLength] = { @@ -718,7 +719,7 @@ TEST(TestHaierACYRW02Class, RealStates) { EXPECT_EQ( "Power: Off, Button: 5 (Power), Mode: 4 (Heat), Temp: 30C, " "Fan: 1 (High), Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, " - "Health: Off, On Timer: Off, Off Timer: Off", + "Health: Off, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); uint8_t expectedState3[kHaierACYRW02StateLength] = { @@ -728,7 +729,7 @@ TEST(TestHaierACYRW02Class, RealStates) { EXPECT_EQ( "Power: On, Button: 1 (Temp Down), Mode: 1 (Cool), Temp: 16C, " "Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " - "Health: On, On Timer: Off, Off Timer: Off", + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); // cool 25, health, fan auto, swing auto, sleep on @@ -739,7 +740,7 @@ TEST(TestHaierACYRW02Class, RealStates) { EXPECT_EQ( "Power: On, Button: 11 (Sleep), Mode: 1 (Cool), Temp: 25C, " "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On, " - "Health: On, On Timer: Off, Off Timer: Off", + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); // cool 25, health, fan 3, swing auto, sleep on @@ -750,7 +751,7 @@ TEST(TestHaierACYRW02Class, RealStates) { EXPECT_EQ( "Power: On, Button: 4 (Fan), Mode: 1 (Cool), Temp: 25C, " "Fan: 1 (High), Turbo: 0 (Off), Swing: 12 (Auto), Sleep: On, " - "Health: On, On Timer: Off, Off Timer: Off", + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); } @@ -981,7 +982,7 @@ TEST(TestDecodeHaierAC_YRW02, RealExample) { EXPECT_EQ( "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 17C, " "Fan: 1 (High), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " - "Health: On, On Timer: Off, Off Timer: Off", + "Health: On, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", haier.toString()); } @@ -1205,7 +1206,7 @@ TEST(TestDecodeHaierAC176, SyntheticDecode) { EXPECT_EQ( "Power: On, Button: 5 (Power), Mode: 1 (Cool), Temp: 24C, Fan: 5 (Auto), " "Turbo: 0 (Off), Swing: 6 (UNKNOWN), Sleep: Off, Health: Off, " - "On Timer: Off, Off Timer: Off", + "Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t result, prev; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &result, &prev)); @@ -1246,7 +1247,7 @@ TEST(TestHaierAC176Class, BuildKnownState) { EXPECT_EQ( "Power: On, Button: 4 (Fan), Mode: 4 (Heat), Temp: 24C, Fan: 1 (High), " "Turbo: 0 (Off), Swing: 0 (Off), Sleep: Off, Health: On, " - "On Timer: Off, Off Timer: Off", + "Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", ac.toString()); /* Disabled pending: https://github.com/crankyoldgit/IRremoteESP8266/issues/1480#issuecomment-885636790 @@ -1260,67 +1261,64 @@ TEST(TestHaierAC176Class, BuildKnownState) { TEST(TestHaierAC176Class, Timers) { IRHaierAC176 ac(kGpioUnused); - EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); EXPECT_EQ(0, ac.getOnTimer()); - EXPECT_FALSE(ac._.OffTimerSet); EXPECT_EQ(0, ac.getOffTimer()); // On Timer ac.setOnTimer(1); - EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ(1, ac.getOnTimer()); ac.setOnTimer(0); - EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); EXPECT_EQ(0, ac.getOnTimer()); ac.setOnTimer(1 * 60); - EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ(1 * 60, ac.getOnTimer()); ac.setOnTimer(2 * 60 + 37); - EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ(2 * 60 + 37, ac.getOnTimer()); ac.setOnTimer(23 * 60 + 59); // Max - EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ(23 * 60 + 59, ac.getOnTimer()); ac.setOnTimer(24 * 60); // Beyond max - EXPECT_TRUE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ(23 * 60 + 59, ac.getOnTimer()); // Max ac.setOnTimer(0); - EXPECT_FALSE(ac._.OnTimerSet); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); EXPECT_EQ(0, ac.getOnTimer()); // Off Timer ac.setOffTimer(1); - EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02OffTimer, ac.getTimerMode()); EXPECT_EQ(1, ac.getOffTimer()); ac.setOffTimer(0); - EXPECT_FALSE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); EXPECT_EQ(0, ac.getOffTimer()); ac.setOffTimer(1 * 60); - EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02OffTimer, ac.getTimerMode()); EXPECT_EQ(1 * 60, ac.getOffTimer()); ac.setOffTimer(2 * 60 + 37); - EXPECT_TRUE(ac._.OffTimerSet); - EXPECT_EQ(2 * 60 + 37, ac.getOffTimer()); - + EXPECT_EQ(kHaierAcYrw02OffTimer, ac.getTimerMode()); ac.setOffTimer(23 * 60 + 59); // Max - EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02OffTimer, ac.getTimerMode()); EXPECT_EQ(23 * 60 + 59, ac.getOffTimer()); ac.setOffTimer(24 * 60); // Beyond max - EXPECT_TRUE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02OffTimer, ac.getTimerMode()); EXPECT_EQ(23 * 60 + 59, ac.getOffTimer()); // Max ac.setOffTimer(0); - EXPECT_FALSE(ac._.OffTimerSet); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); EXPECT_EQ(0, ac.getOffTimer()); // Real data. @@ -1331,16 +1329,50 @@ TEST(TestHaierAC176Class, Timers) { const uint8_t timeroff[22] = { 0xA6, 0x82, 0x00, 0x00, 0x40, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x18, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7}; + // https://docs.google.com/spreadsheets/d/1wdOVS08wgK2pEP7hTZLYMmrQ9FZVmLpZF2HjNQaVxlU/edit#gid=0&range=A65 + const uint8_t timeroffthenon[22] = { + 0xA6, 0x82, 0x00, 0xA0, 0x40, 0xA0, 0x1E, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x10, 0xDE, 0xB7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7}; ac.setRaw(timer30m); + EXPECT_EQ(kHaierAcYrw02OnTimer, ac.getTimerMode()); EXPECT_EQ( "Power: Off, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " - "Health: Off, On Timer: 00:30, Off Timer: Off", + "Health: Off, Timer Mode: 2 (On), On Timer: 00:30, Off Timer: Off", ac.toString()); ac.setRaw(timeroff); + EXPECT_EQ(kHaierAcYrw02NoTimers, ac.getTimerMode()); + EXPECT_EQ( + "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", + ac.toString()); + ac.setRaw(timeroffthenon); + EXPECT_EQ( + "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, Timer Mode: 5 (Off-On), On Timer: 08:00, Off Timer: 00:30", + ac.toString()); + ac.setTimerMode(kHaierAcYrw02OnThenOffTimer); + EXPECT_EQ( + "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, Timer Mode: 4 (On-Off), On Timer: 08:00, Off Timer: 00:30", + ac.toString()); + ac.setTimerMode(kHaierAcYrw02OffTimer); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(30, ac.getOffTimer()); + EXPECT_EQ( + "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " + "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " + "Health: Off, Timer Mode: 1 (Off), On Timer: Off, Off Timer: 00:30", + ac.toString()); + ac.setTimerMode(kHaierAcYrw02NoTimers); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); EXPECT_EQ( "Power: On, Button: 0 (Temp Up), Mode: 0 (Auto), Temp: 24C, " "Fan: 5 (Auto), Turbo: 0 (Off), Swing: 2 (Middle), Sleep: Off, " - "Health: Off, On Timer: Off, Off Timer: Off", + "Health: Off, Timer Mode: 0 (N/A), On Timer: Off, Off Timer: Off", ac.toString()); } From e3fbf66b0c77bfd9ebf7311520cd868c1d658d30 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Mon, 23 Aug 2021 19:21:37 +1000 Subject: [PATCH 4/4] Code review feedback --- src/ir_Haier.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir_Haier.cpp b/src/ir_Haier.cpp index 03a2cd5e3..a43e9400d 100644 --- a/src/ir_Haier.cpp +++ b/src/ir_Haier.cpp @@ -1135,7 +1135,6 @@ bool IRHaierACYRW02::validChecksum(const uint8_t state[], } // End of IRHaierACYRW02 class. - #if (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02) /// Decode the supplied Haier HSU07-HEA03 remote message. /// Status: STABLE / Known to be working.