// Copyright 2018-2021 crankyoldgit /// @file /// @brief Support for Haier A/C protocols. /// The specifics of reverse engineering the protocols details: /// * HSU07-HEA03 by kuzin2006. /// * YR-W02/HSU-09HMC203 by non7top. /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/404 /// @see https://www.dropbox.com/s/mecyib3lhdxc8c6/IR%20data%20reverse%20engineering.xlsx?dl=0 /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/485 /// @see https://www.dropbox.com/sh/w0bt7egp0fjger5/AADRFV6Wg4wZskJVdFvzb8Z0a?dl=0&preview=haer2.ods /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1480 #include "ir_Haier.h" #include #include #ifndef UNIT_TEST #include #endif #include "IRremoteESP8266.h" #include "IRtext.h" #include "IRutils.h" // Constants const uint16_t kHaierAcHdr = 3000; const uint16_t kHaierAcHdrGap = 4300; const uint16_t kHaierAcBitMark = 520; const uint16_t kHaierAcOneSpace = 1650; const uint16_t kHaierAcZeroSpace = 650; const uint32_t kHaierAcMinGap = 150000; // Completely made up value. using irutils::addBoolToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; using irutils::addModelToString; using irutils::addSwingHToString; using irutils::addFanToString; using irutils::addTempToString; using irutils::minsToString; #define GETTIME(x) _.x##Hours * 60 + _.x##Mins #define SETTIME(x, n) do { \ uint16_t mins = n;\ if (n > kHaierAcMaxTime) mins = kHaierAcMaxTime;\ _.x##Hours = mins / 60;\ _.x##Mins = mins % 60;\ } while (0) #if (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC160 || \ SEND_HAIER_AC176) /// Send a Haier A/C formatted message. (HSU07-HEA03 remote) /// Status: STABLE / Known to be working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. void IRsend::sendHaierAC(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes < kHaierACStateLength) return; for (uint16_t r = 0; r <= repeat; r++) { enableIROut(38000); mark(kHaierAcHdr); space(kHaierAcHdr); sendGeneric(kHaierAcHdr, kHaierAcHdrGap, kHaierAcBitMark, kHaierAcOneSpace, kHaierAcBitMark, kHaierAcZeroSpace, kHaierAcBitMark, kHaierAcMinGap, data, nbytes, 38, true, 0, // Repeats handled elsewhere 50); } } #endif // (SEND_HAIER_AC || SEND_HAIER_AC_YRW02 || SEND_HAIER_AC176) #if SEND_HAIER_AC_YRW02 /// Send a Haier YR-W02 remote A/C formatted message. /// Status: STABLE / Known to be working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. void IRsend::sendHaierACYRW02(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes >= kHaierACYRW02StateLength) sendHaierAC(data, nbytes, repeat); } #endif // SEND_HAIER_AC_YRW02 #if SEND_HAIER_AC176 /// Send a Haier 176 bit remote A/C formatted message. /// Status: STABLE / Known to be working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. void IRsend::sendHaierAC176(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes >= kHaierAC176StateLength) sendHaierAC(data, nbytes, repeat); } #endif // SEND_HAIER_AC176 #if SEND_HAIER_AC160 /// Send a Haier 160 bit remote A/C formatted message. /// Status: STABLE / Known to be working. /// @param[in] data The message to be sent. /// @param[in] nbytes The number of bytes of message to be sent. /// @param[in] repeat The number of times the command is to be repeated. void IRsend::sendHaierAC160(const unsigned char data[], const uint16_t nbytes, const uint16_t repeat) { if (nbytes >= kHaierAC160StateLength) sendHaierAC(data, nbytes, repeat); } #endif // SEND_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? IRHaierAC::IRHaierAC(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 IRHaierAC::begin(void) { _irsend.begin(); } #if SEND_HAIER_AC /// Send the current internal state as an IR message. /// @param[in] repeat Nr. of times the message will be repeated. void IRHaierAC::send(const uint16_t repeat) { _irsend.sendHaierAC(getRaw(), kHaierACStateLength, repeat); } #endif // SEND_HAIER_AC /// Calculate and set the checksum values for the internal state. void IRHaierAC::checksum(void) { _.Sum = sumBytes(_.remote_state, kHaierACStateLength - 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 IRHaierAC::validChecksum(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)); } /// Reset the internal state to a fixed known good state. void IRHaierAC::stateReset(void) { std::memset(_.remote_state, 0, sizeof _.remote_state); _.Prefix = kHaierAcPrefix; _.unknown = 1; // const value _.OffHours = 12; // default initial state _.Temp = kHaierAcDefTemp - kHaierAcMinTemp; _.Fan = 3; // kHaierAcFanLow; _.Command = kHaierAcCmdOn; } /// 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* IRHaierAC::getRaw(void) { checksum(); return _.remote_state; } /// Set the internal state from a valid code for this protocol. /// @param[in] new_code A valid code for this protocol. void IRHaierAC::setRaw(const uint8_t new_code[]) { std::memcpy(_.remote_state, new_code, kHaierACStateLength); } /// Set the Command/Button setting of the A/C. /// @param[in] command The value of the command/button that was pressed. void IRHaierAC::setCommand(const uint8_t command) { switch (command) { case kHaierAcCmdOff: case kHaierAcCmdOn: case kHaierAcCmdMode: case kHaierAcCmdFan: case kHaierAcCmdTempUp: case kHaierAcCmdTempDown: case kHaierAcCmdSleep: case kHaierAcCmdTimerSet: case kHaierAcCmdTimerCancel: case kHaierAcCmdHealth: case kHaierAcCmdSwing: _.Command = command; } } /// Get the Command/Button setting of the A/C. /// @return The value of the command/button that was pressed. uint8_t IRHaierAC::getCommand(void) const { return _.Command; } /// Set the speed of the fan. /// @param[in] speed The desired setting. void IRHaierAC::setFan(const uint8_t speed) { uint8_t new_speed = kHaierAcFanAuto; switch (speed) { case kHaierAcFanLow: new_speed = 3; break; case kHaierAcFanMed: new_speed = 2; break; case kHaierAcFanHigh: new_speed = 1; break; // Default to auto for anything else. default: new_speed = kHaierAcFanAuto; } if (speed != getFan()) _.Command = kHaierAcCmdFan; _.Fan = new_speed; } /// Get the current fan speed setting. /// @return The current fan speed. uint8_t IRHaierAC::getFan(void) const { switch (_.Fan) { case 1: return kHaierAcFanHigh; case 2: return kHaierAcFanMed; case 3: return kHaierAcFanLow; default: return kHaierAcFanAuto; } } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRHaierAC::setMode(const uint8_t mode) { uint8_t new_mode = mode; _.Command = kHaierAcCmdMode; // If out of range, default to auto mode. if (mode > kHaierAcFan) new_mode = kHaierAcAuto; _.Mode = new_mode; } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRHaierAC::getMode(void) const { return _.Mode; } /// Set the temperature. /// @param[in] degrees The temperature in degrees celsius. void IRHaierAC::setTemp(const uint8_t degrees) { uint8_t temp = degrees; if (temp < kHaierAcMinTemp) temp = kHaierAcMinTemp; else if (temp > kHaierAcMaxTemp) temp = kHaierAcMaxTemp; uint8_t old_temp = getTemp(); if (old_temp == temp) return; if (old_temp > temp) _.Command = kHaierAcCmdTempDown; else _.Command = kHaierAcCmdTempUp; _.Temp = temp - kHaierAcMinTemp; } /// Get the current temperature setting. /// @return The current setting for temp. in degrees celsius. uint8_t IRHaierAC::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 IRHaierAC::setHealth(const bool on) { _.Command = kHaierAcCmdHealth; _.Health = on; } /// Get the Health (filter) setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRHaierAC::getHealth(void) const { return _.Health; } /// Set the Sleep setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRHaierAC::setSleep(const bool on) { _.Command = kHaierAcCmdSleep; _.Sleep = on; } /// Get the Sleep setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRHaierAC::getSleep(void) const { return _.Sleep; } /// Get the On Timer value/setting of the A/C. /// @return Nr of minutes the timer is set to. -1 is Off/not set etc. int16_t IRHaierAC::getOnTimer(void) const { // Check if the timer is turned on. if (_.OnTimer) return GETTIME(On); else return -1; } /// Get the Off Timer value/setting of the A/C. /// @return Nr of minutes the timer is set to. -1 is Off/not set etc. int16_t IRHaierAC::getOffTimer(void) const { // Check if the timer is turned on. if (_.OffTimer) return GETTIME(Off); else return -1; } /// Get the clock value of the A/C. /// @return The clock time, in Nr of minutes past midnight. uint16_t IRHaierAC::getCurrTime(void) const { return GETTIME(Curr); } /// Set & enable the On Timer. /// @param[in] nr_mins The time expressed in total number of minutes. void IRHaierAC::setOnTimer(const uint16_t nr_mins) { _.Command = kHaierAcCmdTimerSet; _.OnTimer = 1; SETTIME(On, nr_mins); } /// Set & enable the Off Timer. /// @param[in] nr_mins The time expressed in total number of minutes. void IRHaierAC::setOffTimer(const uint16_t nr_mins) { _.Command = kHaierAcCmdTimerSet; _.OffTimer = 1; SETTIME(Off, nr_mins); } /// Cancel/disable the On & Off timers. void IRHaierAC::cancelTimers(void) { _.Command = kHaierAcCmdTimerCancel; _.OffTimer = 0; _.OnTimer = 0; } /// Set the clock value for the A/C. /// @param[in] nr_mins The clock time, in Nr of minutes past midnight. void IRHaierAC::setCurrTime(const uint16_t nr_mins) { SETTIME(Curr, nr_mins); } /// Get the Vertical Swing position setting of the A/C. /// @return The native vertical swing mode. uint8_t IRHaierAC::getSwingV(void) const { return _.SwingV; } /// Set the Vertical Swing mode of the A/C. /// @param[in] state The mode to set the vanes to. void IRHaierAC::setSwingV(const uint8_t state) { if (state == _.SwingV) return; // Nothing to do. switch (state) { case kHaierAcSwingVOff: case kHaierAcSwingVUp: case kHaierAcSwingVDown: case kHaierAcSwingVChg: _.Command = kHaierAcCmdSwing; _.SwingV = state; break; } } /// 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 IRHaierAC::convertMode(const stdAc::opmode_t mode) { switch (mode) { case stdAc::opmode_t::kCool: return kHaierAcCool; case stdAc::opmode_t::kHeat: return kHaierAcHeat; case stdAc::opmode_t::kDry: return kHaierAcDry; case stdAc::opmode_t::kFan: return kHaierAcFan; default: return kHaierAcAuto; } } /// 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 IRHaierAC::convertFan(const stdAc::fanspeed_t speed) { switch (speed) { case stdAc::fanspeed_t::kMin: case stdAc::fanspeed_t::kLow: return kHaierAcFanLow; case stdAc::fanspeed_t::kMedium: return kHaierAcFanMed; case stdAc::fanspeed_t::kHigh: case stdAc::fanspeed_t::kMax: return kHaierAcFanHigh; default: return kHaierAcFanAuto; } } /// 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 IRHaierAC::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: case stdAc::swingv_t::kHigh: case stdAc::swingv_t::kMiddle: return kHaierAcSwingVUp; case stdAc::swingv_t::kLow: case stdAc::swingv_t::kLowest: return kHaierAcSwingVDown; case stdAc::swingv_t::kOff: return kHaierAcSwingVOff; default: return kHaierAcSwingVChg; } } /// 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 IRHaierAC::toCommonMode(const uint8_t mode) { switch (mode) { case kHaierAcCool: return stdAc::opmode_t::kCool; case kHaierAcHeat: return stdAc::opmode_t::kHeat; case kHaierAcDry: return stdAc::opmode_t::kDry; case kHaierAcFan: 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 IRHaierAC::toCommonFanSpeed(const uint8_t speed) { switch (speed) { case kHaierAcFanHigh: return stdAc::fanspeed_t::kMax; case kHaierAcFanMed: return stdAc::fanspeed_t::kMedium; case kHaierAcFanLow: 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 IRHaierAC::toCommonSwingV(const uint8_t pos) { switch (pos) { case kHaierAcSwingVUp: return stdAc::swingv_t::kHighest; case kHaierAcSwingVDown: return stdAc::swingv_t::kLowest; case kHaierAcSwingVOff: return stdAc::swingv_t::kOff; default: return stdAc::swingv_t::kAuto; } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRHaierAC::toCommon(void) const { stdAc::state_t result{}; result.protocol = decode_type_t::HAIER_AC; result.model = -1; // No models used. result.power = true; if (_.Command == kHaierAcCmdOff) result.power = false; result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(getFan()); result.swingv = toCommonSwingV(_.SwingV); result.filter = _.Health; result.sleep = _.Sleep ? 0 : -1; // Not supported. result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.turbo = false; result.econo = false; result.light = false; result.clean = 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 IRHaierAC::toString(void) const { String result = ""; result.reserve(170); // Reserve some heap for the string to reduce fragging. uint8_t cmd = _.Command; result += addIntToString(cmd, kCommandStr, false); result += kSpaceLBraceStr; switch (cmd) { case kHaierAcCmdOff: result += kOffStr; break; case kHaierAcCmdOn: result += kOnStr; break; case kHaierAcCmdMode: result += kModeStr; break; case kHaierAcCmdFan: result += kFanStr; break; case kHaierAcCmdTempUp: result += kTempUpStr; break; case kHaierAcCmdTempDown: result += kTempDownStr; break; case kHaierAcCmdSleep: result += kSleepStr; break; case kHaierAcCmdTimerSet: result += kTimerStr; result += ' '; result += kSetStr; break; case kHaierAcCmdTimerCancel: result += kTimerStr; result += ' '; result += kCancelStr; break; case kHaierAcCmdHealth: result += kHealthStr; break; case kHaierAcCmdSwing: result += kSwingVStr; break; default: result += kUnknownStr; } result += ')'; result += addModeToString(_.Mode, kHaierAcAuto, kHaierAcCool, kHaierAcHeat, kHaierAcDry, kHaierAcFan); result += addTempToString(getTemp()); result += addFanToString(getFan(), kHaierAcFanHigh, kHaierAcFanLow, kHaierAcFanAuto, kHaierAcFanAuto, kHaierAcFanMed); result += addIntToString(_.SwingV, kSwingVStr); result += kSpaceLBraceStr; switch (_.SwingV) { case kHaierAcSwingVOff: result += kOffStr; break; case kHaierAcSwingVUp: result += kUpStr; break; case kHaierAcSwingVDown: result += kDownStr; break; case kHaierAcSwingVChg: result += kChangeStr; break; default: result += kUnknownStr; } result += ')'; result += addBoolToString(_.Sleep, kSleepStr); result += addBoolToString(_.Health, kHealthStr); result += addLabeledString(minsToString(getCurrTime()), kClockStr); result += addLabeledString( getOnTimer() >= 0 ? minsToString(getOnTimer()) : kOffStr, kOnTimerStr); result += addLabeledString( getOffTimer() >= 0 ? minsToString(getOffTimer()) : kOffStr, kOffTimerStr); return result; } // End of IRHaierAC 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? 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 IRHaierAC176::begin(void) { _irsend.begin(); } #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 IRHaierAC176::send(const uint16_t repeat) { _irsend.sendHaierAC176(getRaw(), kHaierAC176StateLength, repeat); } #endif // SEND_HAIER_AC176 /// Calculate and set the checksum values for the internal state. 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 IRHaierAC176::validChecksum(const uint8_t state[], const uint16_t length) { if (length < 2) return false; // 1 byte of data can't have a checksum. if (length < kHaierAC160StateLength) { // 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 IRHaierAC176::stateReset(void) { std::memset(_.raw, 0, sizeof _.raw); _.Model = kHaierAcYrw02ModelA; _.Prefix2 = kHaierAc176Prefix; _.Temp = kHaierAcYrw02DefTempC - kHaierAcYrw02MinTempC; _.Health = true; 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* 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 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 IRHaierAC176::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 kHaierAcYrw02ButtonCFAB: _.Button = button; } } /// Get/Detect the model of the A/C. /// @return The enum of the compatible model. haier_ac176_remote_model_t IRHaierAC176::getModel(void) const { switch (_.Model) { case kHaierAcYrw02ModelB: return haier_ac176_remote_model_t::V9014557_B; default: return haier_ac176_remote_model_t::V9014557_A; } } /// Set the model of the A/C to emulate. /// @param[in] model The enum of the appropriate model. void IRHaierAC176::setModel(haier_ac176_remote_model_t model) { _.Button = kHaierAcYrw02ButtonCFAB; switch (model) { case haier_ac176_remote_model_t::V9014557_B: _.Model = kHaierAcYrw02ModelB; break; default: _.Model = kHaierAcYrw02ModelA; } } /// Get the Button/Command setting of the A/C. /// @return The value of the button/command that was pressed. uint8_t IRHaierAC176::getButton(void) const { return _.Button; } /// Set the operating mode of the A/C. /// @param[in] mode The desired operating mode. void IRHaierAC176::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. } } /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRHaierAC176::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 IRHaierAC176::setUseFahrenheit(const bool on) { _.UseFahrenheit = on; } /// Get the default temperature units in use. /// @return true is Fahrenheit, false is Celsius. bool IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 Health (filter) setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. 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 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 IRHaierAC176::getPower(void) const { return _.Power; } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRHaierAC176::setPower(const bool on) { _.Button = kHaierAcYrw02ButtonPower; _.Power = on; } /// Change the power setting to On. void IRHaierAC176::on(void) { setPower(true); } /// Change the power setting to Off. 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 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 IRHaierAC176::setSleep(const bool on) { _.Button = kHaierAcYrw02ButtonSleep; _.Sleep = on; } /// Get the Turbo setting of the A/C. /// @return The current turbo setting. bool IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::setQuiet(const bool on) { switch (getMode()) { case kHaierAcYrw02Cool: case kHaierAcYrw02Heat: _.Quiet = on; _.Button = kHaierAcYrw02ButtonTurbo; if (on) _.Turbo = false; } } /// Get the current fan speed setting. /// @return The current fan speed. uint8_t IRHaierAC176::getFan(void) const { return _.Fan; } /// Set the speed of the fan. /// @param[in] speed The desired setting. 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; } } /// For backward compatibility. Use getSwingV() instead. /// Get the Vertical Swing position setting of the A/C. /// @return The native position/mode. uint8_t IRHaierAC176::getSwing(void) const { return IRHaierAC176::getSwingV(); } /// For backward compatibility. Use setSwingV() instead. /// Set the Vertical Swing mode of the A/C. /// @param[in] pos The position/mode to set the vanes to. void IRHaierAC176::setSwing(uint8_t pos) { setSwingV(pos); } /// Get the Vertical Swing position setting of the A/C. /// @return The native position/mode. uint8_t IRHaierAC176::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 IRHaierAC176::setSwingV(uint8_t pos) { uint8_t newpos = pos; switch (pos) { case kHaierAcYrw02SwingVOff: case kHaierAcYrw02SwingVAuto: case kHaierAcYrw02SwingVTop: case kHaierAcYrw02SwingVMiddle: case kHaierAcYrw02SwingVBottom: case kHaierAcYrw02SwingVDown: _.Button = kHaierAcYrw02ButtonSwingV; break; default: return; // Unexpected value so don't do anything. } // Heat mode has no MIDDLE setting, use BOTTOM instead. if (pos == kHaierAcYrw02SwingVMiddle && _.Mode == kHaierAcYrw02Heat) newpos = kHaierAcYrw02SwingVBottom; // BOTTOM is only allowed if we are in Heat mode, otherwise MIDDLE. if (pos == kHaierAcYrw02SwingVBottom && _.Mode != kHaierAcYrw02Heat) newpos = kHaierAcYrw02SwingVMiddle; _.SwingV = newpos; } /// Get the Horizontal Swing position setting of the A/C. /// @return The native position/mode. uint8_t IRHaierAC176::getSwingH(void) const { return _.SwingH; } /// Set the Horizontal Swing mode of the A/C. /// @param[in] pos The position/mode to set the vanes to. void IRHaierAC176::setSwingH(uint8_t pos) { switch (pos) { case kHaierAcYrw02SwingHMiddle: case kHaierAcYrw02SwingHLeftMax: case kHaierAcYrw02SwingHLeft: case kHaierAcYrw02SwingHRight: case kHaierAcYrw02SwingHRightMax: case kHaierAcYrw02SwingHAuto: _.Button = kHaierAcYrw02ButtonSwingH; break; default: return; // Unexpected value so don't do anything. } _.SwingH = pos; } /// 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); _.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 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); _.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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::convertSwingV(const stdAc::swingv_t position) { switch (position) { case stdAc::swingv_t::kHighest: case stdAc::swingv_t::kHigh: return kHaierAcYrw02SwingVTop; case stdAc::swingv_t::kMiddle: return kHaierAcYrw02SwingVMiddle; case stdAc::swingv_t::kLow: return kHaierAcYrw02SwingVDown; case stdAc::swingv_t::kLowest: return kHaierAcYrw02SwingVBottom; case stdAc::swingv_t::kOff: return kHaierAcYrw02SwingVOff; default: return kHaierAcYrw02SwingVAuto; } } /// Convert a stdAc::swingh_t enum into it's native setting. /// @param[in] position The enum to be converted. /// @return The native equivalent of the enum. uint8_t IRHaierAC176::convertSwingH(const stdAc::swingh_t position) { switch (position) { case stdAc::swingh_t::kMiddle: return kHaierAcYrw02SwingHMiddle; case stdAc::swingh_t::kLeftMax: return kHaierAcYrw02SwingHLeftMax; case stdAc::swingh_t::kLeft: return kHaierAcYrw02SwingHLeft; case stdAc::swingh_t::kRight: return kHaierAcYrw02SwingHRight; case stdAc::swingh_t::kRightMax: return kHaierAcYrw02SwingHRightMax; case stdAc::swingh_t::kAuto: return kHaierAcYrw02SwingHAuto; default: return kHaierAcYrw02SwingHMiddle; } } /// 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 IRHaierAC176::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 IRHaierAC176::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 IRHaierAC176::toCommonSwingV(const uint8_t pos) { switch (pos) { case kHaierAcYrw02SwingVTop: return stdAc::swingv_t::kHighest; case kHaierAcYrw02SwingVMiddle: return stdAc::swingv_t::kMiddle; case kHaierAcYrw02SwingVDown: return stdAc::swingv_t::kLow; case kHaierAcYrw02SwingVBottom: return stdAc::swingv_t::kLowest; case kHaierAcYrw02SwingVOff: return stdAc::swingv_t::kOff; default: return stdAc::swingv_t::kAuto; } } /// Convert a stdAc::swingh_t enum into it's native setting. /// @param[in] pos The enum to be converted. /// @return The native equivalent of the enum. stdAc::swingh_t IRHaierAC176::toCommonSwingH(const uint8_t pos) { switch (pos) { case kHaierAcYrw02SwingHMiddle: return stdAc::swingh_t::kMiddle; case kHaierAcYrw02SwingHLeftMax: return stdAc::swingh_t::kLeftMax; case kHaierAcYrw02SwingHLeft: return stdAc::swingh_t::kLeft; case kHaierAcYrw02SwingHRight: return stdAc::swingh_t::kRight; case kHaierAcYrw02SwingHRightMax: return stdAc::swingh_t::kRightMax; case kHaierAcYrw02SwingHAuto: return stdAc::swingh_t::kAuto; default: return stdAc::swingh_t::kOff; } } /// Convert the current internal state into its stdAc::state_t equivalent. /// @return The stdAc equivalent of the native settings. stdAc::state_t IRHaierAC176::toCommon(void) const { stdAc::state_t result{}; result.protocol = decode_type_t::HAIER_AC_YRW02; result.model = getModel(); result.power = _.Power; result.mode = toCommonMode(_.Mode); result.celsius = !_.UseFahrenheit; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.Fan); result.swingv = toCommonSwingV(_.SwingV); result.swingh = toCommonSwingH(_.SwingH); result.filter = _.Health; result.sleep = _.Sleep ? 0 : -1; result.turbo = _.Turbo; result.quiet = _.Quiet; // Not supported. result.econo = false; result.light = false; result.clean = 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 IRHaierAC176::toString(void) const { String result = ""; result.reserve(280); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::HAIER_AC176, getModel(), false); result += addBoolToString(_.Power, kPowerStr); 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 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 += addIntToString(_.SwingV, kSwingVStr); result += kSpaceLBraceStr; switch (_.SwingV) { case kHaierAcYrw02SwingVOff: result += kOffStr; break; case kHaierAcYrw02SwingVAuto: result += kAutoStr; break; case kHaierAcYrw02SwingVBottom: result += kLowestStr; break; case kHaierAcYrw02SwingVDown: result += kLowStr; break; case kHaierAcYrw02SwingVTop: result += kHighestStr; break; case kHaierAcYrw02SwingVMiddle: result += kMiddleStr; break; default: result += kUnknownStr; } result += ')'; result += addSwingHToString(_.SwingH, kHaierAcYrw02SwingHAuto, kHaierAcYrw02SwingHLeftMax, kHaierAcYrw02SwingHLeft, kHaierAcYrw02SwingHMiddle, kHaierAcYrw02SwingHRight, kHaierAcYrw02SwingHRightMax, // Below are unused. kHaierAcYrw02SwingHMiddle, kHaierAcYrw02SwingHMiddle, kHaierAcYrw02SwingHMiddle, kHaierAcYrw02SwingHMiddle, kHaierAcYrw02SwingHMiddle); result += addBoolToString(_.Sleep, kSleepStr); result += addBoolToString(_.Health, kHealthStr); 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); 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_HAIER_AC160 || \ DECODE_HAIER_AC176) /// Decode the supplied Haier HSU07-HEA03 remote message. /// Status: STABLE / Known to be working. /// @param[in,out] results Ptr to the data to decode & where to store the decode /// result. /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return A boolean. True if it can decode it, false if it can't. bool IRrecv::decodeHaierAC(decode_results* results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict) { if (nbits != kHaierACBits) return false; // Not strictly a HAIER_AC message. } if (results->rawlen <= (2 * nbits + kHeader) + kFooter - 1 + offset) return false; // Can't possibly be a valid HAIER_AC message. // Pre-Header if (!matchMark(results->rawbuf[offset++], kHaierAcHdr)) return false; if (!matchSpace(results->rawbuf[offset++], kHaierAcHdr)) return false; // Match Header + Data + Footer if (!matchGeneric(results->rawbuf + offset, results->state, results->rawlen - offset, nbits, kHaierAcHdr, kHaierAcHdrGap, kHaierAcBitMark, kHaierAcOneSpace, kHaierAcBitMark, kHaierAcZeroSpace, kHaierAcBitMark, kHaierAcMinGap, true, _tolerance, kMarkExcess)) return false; // Compliance if (strict) { if (results->state[0] != kHaierAcPrefix) return false; if (!IRHaierAC::validChecksum(results->state, nbits / 8)) return false; } // Success results->decode_type = HAIER_AC; results->bits = nbits; return true; } #endif // (DECODE_HAIER_AC || DECODE_HAIER_AC_YRW02) #if DECODE_HAIER_AC_YRW02 /// Decode the supplied Haier YR-W02 remote A/C message. /// Status: BETA / Appears to be working. /// @param[in,out] results Ptr to the data to decode & where to store the decode /// result. /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return A boolean. True if it can decode it, false if it can't. bool IRrecv::decodeHaierACYRW02(decode_results* results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict) { if (nbits != kHaierACYRW02Bits) return false; // Not strictly a HAIER_AC_YRW02 message. } // The protocol is almost exactly the same as HAIER_AC if (!decodeHaierAC(results, offset, nbits, false)) return false; // Compliance if (strict) { if (results->state[0] != kHaierAcYrw02ModelA) return false; if (!IRHaierACYRW02::validChecksum(results->state, nbits / 8)) return false; } // Success // It looks correct, but we haven't check the checksum etc. results->decode_type = HAIER_AC_YRW02; return true; } #endif // DECODE_HAIER_AC_YRW02 #if DECODE_HAIER_AC176 /// Decode the supplied Haier 176 bit remote A/C message. /// Status: STABLE / Known to be working. /// @param[in,out] results Ptr to the data to decode & where to store the decode /// result. /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return A boolean. True if it can decode it, false if it can't. bool IRrecv::decodeHaierAC176(decode_results* results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict) { if (nbits != kHaierAC176Bits) return false; // Not strictly a HAIER_AC176 message. } // The protocol is almost exactly the same as HAIER_AC if (!decodeHaierAC(results, offset, nbits, false)) return false; // Compliance if (strict) { if ((results->state[0] != kHaierAcYrw02ModelA) && (results->state[0] != kHaierAcYrw02ModelB)) return false; if (!IRHaierAC176::validChecksum(results->state, nbits / 8)) return false; } // Success // It looks correct, but we haven't check the checksum etc. results->decode_type = HAIER_AC176; return true; } #endif // DECODE_HAIER_AC176 #if DECODE_HAIER_AC160 /// Decode the supplied Haier 160 bit remote A/C message. /// Status: STABLE / Known to be working. /// @param[in,out] results Ptr to the data to decode & where to store the decode /// result. /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. /// @param[in] nbits The number of data bits to expect. /// @param[in] strict Flag indicating if we should perform strict matching. /// @return A boolean. True if it can decode it, false if it can't. bool IRrecv::decodeHaierAC160(decode_results* results, uint16_t offset, const uint16_t nbits, const bool strict) { if (strict) { if (nbits != kHaierAC160Bits) return false; // Not strictly a HAIER_AC160 message. } // The protocol is almost exactly the same as HAIER_AC if (!decodeHaierAC(results, offset, nbits, false)) return false; // Compliance if (strict) { if (!IRHaierAC176::validChecksum(results->state, nbits / 8)) return false; } // Success // It looks correct, but we haven't check the checksum etc. results->decode_type = HAIER_AC160; 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.