From 23acd958a75b650a2a62052186022535c9e1b2ce Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Mon, 1 Nov 2021 19:54:52 +1000 Subject: [PATCH 1/7] [MIRAGE] Experimental detailed support for KKG29A-C1 remote. * Add support for KKG29A-C1 model. - IFeel - Sensor Temp - Quiet - Filter (UVC) - Clean - On & Off Timers - SwingH * Add model detection. * Add support to `IRac` class. * Update existing unit tests. - Lots more unit tests needed for the new stuff. * Update supported models. For #1573 --- src/IRac.cpp | 35 ++-- src/IRac.h | 6 +- src/IRsend.h | 6 + src/IRtext.cpp | 2 + src/IRtext.h | 2 + src/IRutils.cpp | 7 + src/ir_Mirage.cpp | 453 ++++++++++++++++++++++++++++++++++------ src/ir_Mirage.h | 142 +++++++++++-- src/locale/defaults.h | 6 + test/IRac_test.cpp | 67 ++++-- test/ir_Mirage_test.cpp | 63 ++++-- 11 files changed, 664 insertions(+), 125 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index cff285471..f8df60b1d 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1481,36 +1481,41 @@ void IRac::midea(IRMideaAC *ac, #if SEND_MIRAGE /// Send a Mirage 120-bit A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. +/// @param[in] model The A/C model 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] swingh The horizontal swing setting. /// @param[in] turbo Run the device in turbo mode. +/// @param[in] quiet Run the device in quiet/silent mode. /// @param[in] light Turn on the Light/Display. +/// @param[in] filter Turn on the (UVC/ion/pollen/etc) filter mode. +/// @param[in] clean Turn on the self-cleaning mode. e.g. XFan, dry filters etc /// @param[in] sleep The time in Nr. of mins to sleep for. < 0 is ignore. /// @note Sleep is either on or off. The time is useless. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. -void IRac::mirage(IRMirageAc *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 light, +void IRac::mirage(IRMirageAc *ac, const mirage_ac_remote_model_t model, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool quiet, const bool light, + const bool filter, const bool clean, const int16_t sleep, const int16_t clock) { ac->begin(); - + ac->setModel(model); ac->setPower(on); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); ac->setFan(ac->convertFan(fan)); ac->setSwingV(ac->convertSwingV(swingv)); - // No SwingH setting available + ac->setSwingH(swingh != stdAc::swingh_t::kOff); ac->setTurbo(turbo); - // No Quiet setting available. + ac->setQuiet(quiet); ac->setLight(light); - // No Filter setting available. - // No Clean setting available. + ac->setFilter(filter); + ac->setCleanToggle(clean); // No Beep setting available. ac->setSleep(sleep >= 0); if (clock >= 0) ac->setClock(clock * 60); // Clock is in seconds. @@ -2526,6 +2531,9 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, case decode_type_t::WHIRLPOOL_AC: result.power = desired.power ^ prev->power; break; + case decode_type_t::MIRAGE: + result.clean = desired.clean ^ prev->clean; + break; case decode_type_t::PANASONIC_AC: // CKP models use a power mode toggle. if (desired.model == panasonic_ac_remote_model_t::kPanasonicCkp) @@ -2897,8 +2905,9 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { case MIRAGE: { IRMirageAc ac(_pin, _inverted, _modulation); - mirage(&ac, send.power, send.mode, degC, - send.fanspeed, send.swingv, send.turbo, send.light, + mirage(&ac, (mirage_ac_remote_model_t)send.model, send.power, send.mode, + degC, send.fanspeed, send.swingv, send.swingh, + send.turbo, send.quiet, send.light, send.filter, send.clean, send.sleep, send.clock); break; } diff --git a/src/IRac.h b/src/IRac.h index c0ae4921f..84f179b51 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -330,10 +330,12 @@ void electra(IRElectraAc *ac, const bool light, const int16_t sleep = -1); #endif // SEND_MIDEA #if SEND_MIRAGE - void mirage(IRMirageAc *ac, + void mirage(IRMirageAc *ac, const mirage_ac_remote_model_t model, 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 light, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool quiet, const bool light, + const bool filter, const bool clean, const int16_t sleep = -1, const int16_t clock = -1); #endif // SEND_MIRAGE #if SEND_MITSUBISHI_AC diff --git a/src/IRsend.h b/src/IRsend.h index c09ec3d4c..7ab8dafd4 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -142,6 +142,12 @@ enum hitachi_ac1_remote_model_t { R_LT0541_HTA_B, // (2) R-LT0541-HTA Remote in "B" setting. }; +/// MIRAGE A/C model numbers +enum mirage_ac_remote_model_t { + KKG9AC1 = 1, // (1) KKG9A-C1 Remote. (Default) + KKG29AC1, // (2) KKG29A-C1 Remote. +}; + /// Panasonic A/C model numbers enum panasonic_ac_remote_model_t { kPanasonicUnknown = 0, diff --git a/src/IRtext.cpp b/src/IRtext.cpp index 2165ba8f3..0b6a55b5f 100644 --- a/src/IRtext.cpp +++ b/src/IRtext.cpp @@ -241,6 +241,8 @@ IRTEXT_CONST_STRING(kGe6711ar2853mStr, D_STR_GE6711AR2853M); ///< IRTEXT_CONST_STRING(kAkb75215403Str, D_STR_AKB75215403); ///< "AKB75215403" IRTEXT_CONST_STRING(kAkb74955603Str, D_STR_AKB74955603); ///< "AKB74955603" IRTEXT_CONST_STRING(kAkb73757604Str, D_STR_AKB73757604); ///< "AKB73757604" +IRTEXT_CONST_STRING(kKkg9ac1Str, D_STR_KKG9AC1); ///< "KKG9AC1" +IRTEXT_CONST_STRING(kKkg29ac1Str, D_STR_KKG29AC1); ///< "KKG29AC1" IRTEXT_CONST_STRING(kLkeStr, D_STR_LKE); ///< "LKE" IRTEXT_CONST_STRING(kNkeStr, D_STR_NKE); ///< "NKE" IRTEXT_CONST_STRING(kDkeStr, D_STR_DKE); ///< "DKE" diff --git a/src/IRtext.h b/src/IRtext.h index 929f064c0..2b4e45859 100644 --- a/src/IRtext.h +++ b/src/IRtext.h @@ -111,6 +111,8 @@ extern IRTEXT_CONST_PTR(kIFeelStr); extern IRTEXT_CONST_PTR(kInsideStr); extern IRTEXT_CONST_PTR(kIonStr); extern IRTEXT_CONST_PTR(kJkeStr); +extern IRTEXT_CONST_PTR(kKkg29ac1Str); +extern IRTEXT_CONST_PTR(kKkg9ac1Str); extern IRTEXT_CONST_PTR(kLastStr); extern IRTEXT_CONST_PTR(kLeftMaxNoSpaceStr); extern IRTEXT_CONST_PTR(kLeftMaxStr); diff --git a/src/IRutils.cpp b/src/IRutils.cpp index c52db342d..148c6ea35 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -626,6 +626,13 @@ namespace irutils { default: return kUnknownStr; } break; + case decode_type_t::MIRAGE: + switch (model) { + case mirage_ac_remote_model_t::KKG9AC1: return kKkg9ac1Str; + case mirage_ac_remote_model_t::KKG29AC1: return kKkg29ac1Str; + default: return kUnknownStr; + } + break; case decode_type_t::PANASONIC_AC: switch (model) { case panasonic_ac_remote_model_t::kPanasonicLke: return kLkeStr; diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index d7c9fda14..a4f988677 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -21,9 +21,11 @@ using irutils::addFanToString; using irutils::addIntToString; using irutils::addLabeledString; using irutils::addModeToString; +using irutils::addModelToString; using irutils::addSwingHToString; using irutils::addSwingVToString; using irutils::addTempToString; +using irutils::addToggleToString; using irutils::minsToString; using irutils::bcdToUint8; using irutils::uint8ToBcd; @@ -129,6 +131,60 @@ uint8_t *IRMirageAc::getRaw(void) { /// @param[in] data A valid code for this protocol. void IRMirageAc::setRaw(const uint8_t *data) { std::memcpy(_.raw, data, kMirageStateLength); + _model = getModel(true); +} + +/// Guess the Mirage remote model from the supplied state code. +/// @param[in] state A valid state code for this protocol. +/// @return The model code. +mirage_ac_remote_model_t IRMirageAc::getModel(const uint8_t *state) { + Mirage120Protocol prot; + std::memcpy(prot.raw, state, kMirageStateLength); + // If the minutes or seconds or raw[10] are set, it's a KKG9AC1 + if (prot.Minutes || prot.Seconds || prot.raw[10]) + return mirage_ac_remote_model_t::KKG9AC1; + // Check for KKG29AC1 specific settings. + if (prot.RecycleHeat || prot.Filter || prot.Sleep_Kkg29ac1 || + prot.CleanToggle || prot.IFeel) + return mirage_ac_remote_model_t::KKG29AC1; + else + return mirage_ac_remote_model_t::KKG9AC1; +} + +/// Get the model code of the interal message state. +/// @param[in] useRaw If set, we try to get the model info from just the state. +/// @return The model code. +mirage_ac_remote_model_t IRMirageAc::getModel(const bool useRaw) const { + return useRaw ? getModel(_.raw) : _model; +} + +/// Set the model code of the interal message state. +/// @param[in] model The desired model to use for the settings. +void IRMirageAc::setModel(mirage_ac_remote_model_t model) { + if (model != _model) { // Only change things if we need to. + // Save the old settings. + stdAc::state_t old = toCommon(); + uint16_t ontimer = getOnTimer(); + uint16_t offtimer = getOffTimer(); + // Change the model. + _model = model; + // Restore/Convert the settings. + setPower(old.power); + setTemp(old.degrees); + setMode(convertMode(old.mode)); + setFan(convertFan(old.fanspeed)); + setTurbo(old.turbo); + setSleep(old.sleep >= 0); + setLight(old.light); + setSwingV(convertSwingV(old.swingv)); + setSwingH(old.swingh != stdAc::swingh_t::kOff); + setQuiet(old.quiet); + setCleanToggle(old.clean); + setFilter(old.filter); + setClock(old.clock * 60); // setClock() expects seconds, not minutes. + setOnTimer(ontimer); + setOffTimer(offtimer); + } } /// Calculate and set the checksum values for the internal state. @@ -157,30 +213,40 @@ void IRMirageAc::off(void) { setPower(false); } /// Change the power setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMirageAc::setPower(bool on) { - // In order to change the power setting, it seems must be less than - // kMirageAcPowerOff. kMirageAcPowerOff is larger than half of the possible - // value stored in the allocated bit space. - // Thus if the value is larger than kMirageAcPowerOff the power is off. - // Less than, then power is on. - // We can't just aribitarily add or subtract the value (which analysis - // indicates is how the power status changes. Very weird, I know!) as that is - // not an idempotent action, we must check if the addition or substraction is - // needed first. e.g. via getPower() - // i.e. If we added or subtracted twice, we would cause a wrap of the integer - // and not get the desired result. - if (on) - _.SwingAndPower -= getPower() ? 0 : kMirageAcPowerOff; - else - _.SwingAndPower += getPower() ? kMirageAcPowerOff : 0; + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Power = on ? 0b11 : 0b00; + break; + default: + // In order to change the power setting, it seems must be less than + // kMirageAcPowerOff. kMirageAcPowerOff is larger than half of the + // possible value stored in the allocated bit space. + // Thus if the value is larger than kMirageAcPowerOff the power is off. + // Less than, then power is on. + // We can't just aribitarily add or subtract the value (which analysis + // indicates is how the power status changes. Very weird, I know!) as that + // is not an idempotent action, we must check if the addition or + // substraction is needed first. e.g. via getPower() + // i.e. If we added or subtracted twice, we would cause a wrap of the + // integer and not get the desired result. + if (on) + _.SwingAndPower -= getPower() ? 0 : kMirageAcPowerOff; + else + _.SwingAndPower += getPower() ? kMirageAcPowerOff : 0; + } } /// Get the value of the current power setting. /// @return true, the setting is on. false, the setting is off. bool IRMirageAc::getPower(void) const { - return _.SwingAndPower < kMirageAcPowerOff; + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.Power == 0b11; + default: + return _.SwingAndPower < kMirageAcPowerOff; + } } - /// Get the operating mode setting of the A/C. /// @return The current operating mode setting. uint8_t IRMirageAc::getMode(void) const { return _.Mode; } @@ -228,62 +294,118 @@ uint8_t IRMirageAc::getFan(void) const { return _.Fan; } /// Change the Turbo setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRMirageAc::setTurbo(bool on) { - _.Turbo = (on && (getMode() == kMirageAcCool)); + const bool value = (on && (getMode() == kMirageAcCool)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Turbo_Kkg29ac1 = value; + break; + default: + _.Turbo_Kkg9ac1 = value; + } } /// Get the value of the current Turbo setting. /// @return true, the setting is on. false, the setting is off. -bool IRMirageAc::getTurbo(void) const { return _.Turbo; } +bool IRMirageAc::getTurbo(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Turbo_Kkg29ac1; + default: return _.Turbo_Kkg9ac1; + } +} /// Change the Sleep setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRMirageAc::setSleep(bool on) { _.Sleep = on; } +void IRMirageAc::setSleep(bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Sleep_Kkg29ac1 = on; + break; + default: + _.Sleep_Kkg9ac1 = on; + } +} /// Get the value of the current Sleep setting. /// @return true, the setting is on. false, the setting is off. -bool IRMirageAc::getSleep(void) const { return _.Sleep; } +bool IRMirageAc::getSleep(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Sleep_Kkg29ac1; + default: return _.Sleep_Kkg9ac1; + } +} /// Change the Light/Display setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRMirageAc::setLight(bool on) { _.Light = on; } +void IRMirageAc::setLight(bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.LightToggle_Kkg29ac1 = on; + break; + default: + _.Light_Kkg9ac1 = on; + } +} /// Get the value of the current Light/Display setting. /// @return true, the setting is on. false, the setting is off. -bool IRMirageAc::getLight(void) const { return _.Light; } +bool IRMirageAc::getLight(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.LightToggle_Kkg29ac1; + default: return _.Light_Kkg9ac1; + } +} /// Get the clock time of the A/C unit. /// @return Nr. of seconds past midnight. uint32_t IRMirageAc::getClock(void) const { - return ((bcdToUint8(_.Hours) * 60) + bcdToUint8(_.Minutes)) * 60 + - bcdToUint8(_.Seconds); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return 0; + default: + return ((bcdToUint8(_.Hours) * 60) + bcdToUint8(_.Minutes)) * 60 + + bcdToUint8(_.Seconds); + } } /// Set the clock time on the A/C unit. /// @param[in] nr_of_seconds Nr. of seconds past midnight. void IRMirageAc::setClock(const uint32_t nr_of_seconds) { - uint32_t remaining = std::min( - nr_of_seconds, (uint32_t)(24 * 60 * 60 - 1)); // Limit to 23:59:59. - _.Seconds = uint8ToBcd(remaining % 60); - remaining /= 60; - _.Minutes = uint8ToBcd(remaining % 60); - remaining /= 60; - _.Hours = uint8ToBcd(remaining); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Minutes = _.Seconds = 0; // No clock setting. Clear it just in case. + break; + default: + uint32_t remaining = std::min( + nr_of_seconds, (uint32_t)(24 * 60 * 60 - 1)); // Limit to 23:59:59. + _.Seconds = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Minutes = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Hours = uint8ToBcd(remaining); + } } /// Set the Vertical Swing setting/position of the A/C. /// @param[in] position The desired swing setting. void IRMirageAc::setSwingV(const uint8_t position) { - const bool power = getPower(); switch (position) { + case kMirageAcSwingVOff: case kMirageAcSwingVLowest: case kMirageAcSwingVLow: case kMirageAcSwingVMiddle: case kMirageAcSwingVHigh: case kMirageAcSwingVHighest: case kMirageAcSwingVAuto: - _.SwingAndPower = position; - // Power needs to be reapplied after overwriting SwingAndPower - setPower(power); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SwingV = (position != kMirageAcSwingVOff); + break; + default: + const bool power = getPower(); + _.SwingAndPower = position; + // Power needs to be reapplied after overwriting SwingAndPower + setPower(power); + } break; default: // Default to Auto for anything else. setSwingV(kMirageAcSwingVAuto); @@ -293,7 +415,194 @@ void IRMirageAc::setSwingV(const uint8_t position) { /// Get the Vertical Swing setting/position of the A/C. /// @return The desired Vertical Swing setting/position. uint8_t IRMirageAc::getSwingV(void) const { - return _.SwingAndPower - (getPower() ? 0 : kMirageAcPowerOff); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.SwingV; + default: + return _.SwingAndPower - (getPower() ? 0 : kMirageAcPowerOff); + } +} + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setSwingH(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SwingH = on; + break; + default: + break; + } +} + +/// Get the Horizontal Swing setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getSwingH(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.SwingH; + default: return false; + } +} + +/// Set the Quiet setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setQuiet(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Quiet = on; + break; + default: + break; + } +} + +/// Get the Quiet setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getQuiet(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Quiet; + default: return false; + } +} + +/// Set the CleanToggle setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setCleanToggle(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.CleanToggle = on; + break; + default: + break; + } +} + +/// Get the Clean Toggle setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getCleanToggle(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.CleanToggle; + default: return false; + } +} + +/// Set the Filter setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setFilter(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.Filter = on; + break; + default: + break; + } +} + +/// Get the Filter setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getFilter(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.Filter; + default: return false; + } +} + +/// Set the IFeel setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setIFeel(const bool on) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.IFeel = on; + if (!on) _.SensorTemp = 0; + break; + default: + break; + } +} + +/// Get the IFeel setting of the A/C. +/// @return on true, the setting is on. false, the setting is off. +bool IRMirageAc::getIFeel(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: return _.IFeel; + default: return false; + } +} + +/// Set the Sensor Temp setting of the A/C's remote. +/// @param[in] degrees The desired sensor temp. in degrees celsius. +void IRMirageAc::setSensorTemp(const uint8_t degrees) { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.SensorTemp = std::min(kMirageAcSensorTempMax, degrees) + + kMirageAcSensorTempOffset; + break; + default: + break; + } +} + +/// Get the Sensor Temp setting of the A/C's remote. +/// @return The current setting for the sensor temp. in degrees celsius. +uint16_t IRMirageAc::getSensorTemp(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.SensorTemp - kMirageAcSensorTempOffset; + default: + return false; + } +} + +/// Get the number of minutes the On Timer is currently set for. +/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use. +uint16_t IRMirageAc::getOnTimer(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.OnTimerEnable ? _.OnTimerHours * 60 + _.OnTimerMins : 0; + default: + return 0; + } +} + +/// Set the number of minutes for the On Timer. +/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer. +void IRMirageAc::setOnTimer(const uint16_t nr_of_mins) { + uint16_t mins = std::min(nr_of_mins, (uint16_t)(24 * 60)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.OnTimerEnable = (mins > 0); + _.OnTimerHours = mins / 60; + _.OnTimerMins = mins % 60; + break; + default: + break; + } +} + +/// Get the number of minutes the Off Timer is currently set for. +/// @return Nr. of Minutes the timer is set for. 0, is the timer is not in use. +uint16_t IRMirageAc::getOffTimer(void) const { + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + return _.OffTimerEnable ? _.OffTimerHours * 60 + _.OffTimerMins : 0; + default: + return 0; + } +} + +/// Set the number of minutes for the Off Timer. +/// @param[in] nr_of_mins How long to set the timer for. 0 disables the timer. +void IRMirageAc::setOffTimer(const uint16_t nr_of_mins) { + uint16_t mins = std::min(nr_of_mins, (uint16_t)(24 * 60)); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + _.OffTimerEnable = (mins > 0); + _.OffTimerHours = mins / 60; + _.OffTimerMins = mins % 60; + break; + default: + break; + } } /// Convert a native mode into its stdAc equivalent. @@ -356,6 +665,7 @@ uint8_t IRMirageAc::convertSwingV(const stdAc::swingv_t position) { case stdAc::swingv_t::kMiddle: return kMirageAcSwingVMiddle; case stdAc::swingv_t::kLow: return kMirageAcSwingVLow; case stdAc::swingv_t::kLowest: return kMirageAcSwingVLowest; + case stdAc::swingv_t::kOff: return kMirageAcSwingVOff; default: return kMirageAcSwingVAuto; } } @@ -370,7 +680,8 @@ stdAc::swingv_t IRMirageAc::toCommonSwingV(const uint8_t pos) { case kMirageAcSwingVMiddle: return stdAc::swingv_t::kMiddle; case kMirageAcSwingVLow: return stdAc::swingv_t::kLow; case kMirageAcSwingVLowest: return stdAc::swingv_t::kLowest; - default: return stdAc::swingv_t::kAuto; + case kMirageAcSwingVAuto: return stdAc::swingv_t::kAuto; + default: return stdAc::swingv_t::kOff; } } @@ -379,24 +690,24 @@ stdAc::swingv_t IRMirageAc::toCommonSwingV(const uint8_t pos) { stdAc::state_t IRMirageAc::toCommon(void) const { stdAc::state_t result; result.protocol = decode_type_t::MIRAGE; - result.model = -1; // No models used. + result.model = _model; result.power = getPower(); result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(getFan()); result.swingv = toCommonSwingV(getSwingV()); + result.swingh = getSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; result.turbo = getTurbo(); result.light = getLight(); + result.clean = getCleanToggle(); + result.filter = getFilter(); result.sleep = getSleep() ? 0 : -1; + result.quiet = getQuiet(); + result.clock = getClock() / 60; // Not supported. - result.swingh = stdAc::swingh_t::kOff; - result.quiet = false; - result.clean = false; result.econo = false; - result.filter = false; result.beep = false; - result.clock = -1; return result; } @@ -404,8 +715,9 @@ stdAc::state_t IRMirageAc::toCommon(void) const { /// @return A string containing the settings in human-readable form. String IRMirageAc::toString(void) const { String result = ""; - result.reserve(110); // Reserve some heap for the string to reduce fragging. - result += addBoolToString(getPower(), kPowerStr, false); + result.reserve(240); // Reserve some heap for the string to reduce fragging. + result += addModelToString(decode_type_t::MIRAGE, _model, false); + result += addBoolToString(getPower(), kPowerStr); result += addModeToString(_.Mode, 0xFF, kMirageAcCool, kMirageAcHeat, kMirageAcDry, kMirageAcFan); @@ -414,20 +726,41 @@ String IRMirageAc::toString(void) const { kMirageAcFanLow, kMirageAcFanAuto, kMirageAcFanAuto, kMirageAcFanMed); - result += addSwingVToString(getSwingV(), - kMirageAcSwingVAuto, - kMirageAcSwingVHighest, - kMirageAcSwingVHigh, - 0xFF, // Unused. - kMirageAcSwingVMiddle, - 0xFF, // Unused. - kMirageAcSwingVLow, - kMirageAcSwingVLowest, - 0xFF, 0xFF, 0xFF, 0xFF); // Unused. - result += addBoolToString(_.Turbo, kTurboStr); - result += addBoolToString(_.Light, kLightStr); - result += addBoolToString(_.Sleep, kSleepStr); - result += addLabeledString(minsToString(getClock() / 60), kClockStr); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getLight(), kLightStr); + result += addBoolToString(getSleep(), kSleepStr); + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + result += addBoolToString(_.SwingV, kSwingVStr); + result += addBoolToString(_.SwingH, kSwingHStr); + result += addBoolToString(_.Filter, kFilterStr); + result += addToggleToString(_.CleanToggle, kCleanStr); + result += addLabeledString(getOnTimer() ? minsToString(getOnTimer()) + : kOffStr, + kOnTimerStr); + result += addLabeledString(getOffTimer() ? minsToString(getOffTimer()) + : kOffStr, + kOffTimerStr); + result += addBoolToString(_.IFeel, kIFeelStr); + if (_.IFeel) { + result += addIntToString(getSensorTemp(), kSensorTempStr); + result += 'C'; + } + break; + default: + result += addSwingVToString(getSwingV(), + kMirageAcSwingVAuto, + kMirageAcSwingVHighest, + kMirageAcSwingVHigh, + 0xFF, // Unused. + kMirageAcSwingVMiddle, + 0xFF, // Unused. + kMirageAcSwingVLow, + kMirageAcSwingVLowest, + kMirageAcSwingVOff, + 0xFF, 0xFF, 0xFF); // Unused. + result += addLabeledString(minsToString(getClock() / 60), kClockStr); + } return result; } #endif // DECODE_MIRAGE diff --git a/src/ir_Mirage.h b/src/ir_Mirage.h index 4242b93ef..e999fad70 100644 --- a/src/ir_Mirage.h +++ b/src/ir_Mirage.h @@ -9,6 +9,8 @@ // Brand: Mirage, Model: VLU series A/C // Brand: Maxell, Model: MX-CH18CF A/C // Brand: Maxell, Model: KKG9A-C1 remote +// Brand: Tronitechnik, Model: A/C +// Brand: Tronitechnik, Model: KKG29A-C1 remote #ifndef IR_MIRAGE_H_ #define IR_MIRAGE_H_ @@ -28,35 +30,67 @@ /// @see https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0 union Mirage120Protocol{ uint8_t raw[kMirageStateLength]; ///< The state in code form. - struct { + struct { // Common // Byte 0 - uint8_t :8; // Header. (0x56) + uint8_t Header :8; // Header. (0x56) // Byte 1 uint8_t Temp :8; // Celsius minus 0x5C. // Byte 2 - uint8_t :8; // Unknown / Unused. Typically 0x00 + uint8_t :8; // Unknown / Unused. // Byte 3 - uint8_t :3; // Unknown / Unused. Typically 0x0 - uint8_t Light :1; // Aka. Display. Seems linked to Sleep mode. - uint8_t :4; // Unknown / Unused. Typically 0x0 + uint8_t :8; // Unknown / Unused. // Byte 4 uint8_t Fan :2; // Fan Speed. - uint8_t :2; // Unknown / Unused. Typically 0x0 + uint8_t :2; // Unknown / Unused. uint8_t Mode :4; // Cool, Heat, Dry, Fan, Recycle // Byte 5 + uint8_t :8; + // Byte 6 + uint8_t :8; + // Byte 7 + uint8_t :8; + // Byte 8 + uint8_t :8; + // Byte 9 + uint8_t :8; + // Byte 10 + uint8_t :8; + // Byte 11 + uint8_t :8; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :8; + // Byte 14 + uint8_t Sum :8; // Sum of all the previous nibbles. + }; + struct { // KKG9AC1 remote + // Byte 0 + uint8_t :8; // Header + // Byte 1 + uint8_t :8; // Temp + // Byte 2 + uint8_t :8; // Unknown / Unused. + // Byte 3 + uint8_t :3; // Unknown / Unused. + uint8_t Light_Kkg9ac1 :1; // Aka. Display. Seems linked to Sleep mode. + uint8_t :4; // Unknown / Unused. + // Byte 4 + uint8_t :8; // Fan & Mode + // Byte 5 uint8_t :1; // Unknown uint8_t SwingAndPower :7; // Byte 6 - uint8_t :7; // Unknown / Unused. Typically 0x00 - uint8_t Sleep :1; // Sleep mode on or off. + uint8_t :7; // Unknown / Unused. + uint8_t Sleep_Kkg9ac1 :1; // Sleep mode on or off. // Byte 7 - uint8_t :3; // Unknown / Unused. Typically 0x0 - uint8_t Turbo :1; // Sleep mode on or off. Only works in Cool mode. - uint8_t :4; // Unknown / Unused. Typically 0x0 + uint8_t :3; // Unknown / Unused. + uint8_t Turbo_Kkg9ac1 :1; // Turbo mode on or off. Only works in Cool mode. + uint8_t :4; // Unknown / Unused. // Byte 8 - uint8_t :8; // Unknown / Unused. Typically 0xC0 + uint8_t :8; // Unknown / Unused. // Byte 9 - uint8_t :8; // Unknown / Unused. Typically 0x00 + uint8_t :8; // Unknown / Unused. // Byte 10 uint8_t :8; // Unknown / Unused. // Byte 11 @@ -66,7 +100,61 @@ union Mirage120Protocol{ // Byte 13 uint8_t Hours :8; // Nr. of Hours in BCD. // Byte 14 - uint8_t Sum :8; // Sum of all the previous nibbles. + uint8_t :8; // Sum + }; + struct { // KKG29A-C1 remote + // Byte 0 + uint8_t :8; // Header + // Byte 1 + uint8_t :8; // Temp + // Byte 2 + uint8_t :8; + // Byte 3 + uint8_t Quiet :1; + uint8_t :7; + // Byte 4 + uint8_t :2; // Fan + uint8_t OffTimerEnable :1; + uint8_t OnTimerEnable :1; + uint8_t :3; // Mode + uint8_t :1; + // Byte 5 + uint8_t SwingH :1; + uint8_t SwingV :1; + uint8_t LightToggle_Kkg29ac1 :1; // Aka. Display Toggle. + uint8_t :3; + uint8_t Power :2; + // Byte 6 + uint8_t :1; + uint8_t Filter :1; // Aka. UVC + uint8_t :1; + uint8_t Sleep_Kkg29ac1 :1; // Sleep mode on or off. + uint8_t :2; + uint8_t RecycleHeat :1; + uint8_t :1; + // Byte 7 + uint8_t SensorTemp :6; // Temperature at the remote + uint8_t CleanToggle :1; + uint8_t IFeel :1; + // Byte 8 + uint8_t OnTimerHours :5; + uint8_t :2; + uint8_t Turbo_Kkg29ac1 :1; // Turbo mode on or off. + // Byte 9 + uint8_t OnTimerMins :6; + uint8_t :2; + // Byte 10 + uint8_t OffTimerHours :5; + uint8_t :3; + // Byte 11 + uint8_t OffTimerMins :6; + uint8_t :2; + // Byte 12 + uint8_t :8; + // Byte 13 + uint8_t :8; + // Byte 14 + uint8_t :8; // Sum }; }; @@ -85,8 +173,11 @@ const uint8_t kMirageAcFanLow = 0b11; // 3 const uint8_t kMirageAcMinTemp = 16; // 16C const uint8_t kMirageAcMaxTemp = 32; // 32C const uint8_t kMirageAcTempOffset = 0x5C; +const uint8_t kMirageAcSensorTempOffset = 20; +const uint8_t kMirageAcSensorTempMax = 43; // Celsius const uint8_t kMirageAcPowerOff = 0x5F; +const uint8_t kMirageAcSwingVOff = 0b0000; // 0 const uint8_t kMirageAcSwingVLowest = 0b0011; // 3 const uint8_t kMirageAcSwingVLow = 0b0101; // 5 const uint8_t kMirageAcSwingVMiddle = 0b0111; // 7 @@ -134,7 +225,25 @@ class IRMirageAc { bool getSleep(void) const; void setSwingV(const uint8_t position); uint8_t getSwingV(void) const; - + void setSwingH(const bool on); + bool getSwingH(void) const; + void setQuiet(const bool on); + bool getQuiet(void) const; + void setCleanToggle(const bool on); + bool getCleanToggle(void) const; + void setFilter(const bool on); + bool getFilter(void) const; + void setIFeel(const bool on); + bool getIFeel(void) const; + void setSensorTemp(const uint8_t degrees); + uint16_t getSensorTemp(void) const; + uint16_t getOnTimer(void) const; + uint16_t getOffTimer(void) const; + void setOnTimer(const uint16_t nr_of_mins); + void setOffTimer(const uint16_t nr_of_mins); + mirage_ac_remote_model_t getModel(const bool useRaw = false) const; + void setModel(mirage_ac_remote_model_t model); + static mirage_ac_remote_model_t getModel(const uint8_t *state); static bool validChecksum(const uint8_t* data); static uint8_t calculateChecksum(const uint8_t* data); static uint8_t convertMode(const stdAc::opmode_t mode); @@ -155,6 +264,7 @@ class IRMirageAc { /// @endcond #endif // UNIT_TEST Mirage120Protocol _; + mirage_ac_remote_model_t _model; void checksum(void); }; #endif // IR_MIRAGE_H_ diff --git a/src/locale/defaults.h b/src/locale/defaults.h index 55d1ed42b..127e7f470 100644 --- a/src/locale/defaults.h +++ b/src/locale/defaults.h @@ -586,6 +586,12 @@ #ifndef D_STR_AKB73757604 #define D_STR_AKB73757604 "AKB73757604" #endif // D_STR_AKB73757604 +#ifndef D_STR_KKG9AC1 +#define D_STR_KKG9AC1 "KKG9AC1" +#endif // D_STR_KKG9AC1 +#ifndef D_STR_KKG29AC1 +#define D_STR_KKG29AC1 "KKG29AC1" +#endif // D_STR_KKG9AC1 #ifndef D_STR_LKE #define D_STR_LKE "LKE" #endif // D_STR_LKE diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 5abec2367..69a4f861a 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1352,30 +1352,65 @@ TEST(TestIRac, Mirage) { IRMirageAc ac(kGpioUnused); IRac irac(kGpioUnused); IRrecv capture(kGpioUnused); - char expected[] = - "Power: On, Mode: 3 (Dry), Temp: 27C, Fan: 2 (Medium), " - "Swing(V): 9 (High), " - "Turbo: Off, Light: Off, Sleep: On, Clock: 17:31"; + stdAc::state_t r, p; + const char expected_KKG9AC1[] = + "Model: 1 (KKG9AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " + "Fan: 2 (Medium), Turbo: Off, Light: Off, Sleep: On, " + "Swing(V): 9 (High), Clock: 17:31"; ac.begin(); irac.mirage(&ac, - true, // Power - stdAc::opmode_t::kDry, // Mode - 27, // Degrees (Celsius) - stdAc::fanspeed_t::kMedium, // Fan speed - stdAc::swingv_t::kHigh, // Veritical Swing - false, // Turbo - false, // Light - 8 * 60 + 0, // Sleep time - 17 * 60 + 31); // Clock + mirage_ac_remote_model_t::KKG9AC1, // Model + true, // Power + stdAc::opmode_t::kDry, // Mode + 27, // Degrees (Celsius) + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kHigh, // Veritical Swing + stdAc::swingh_t::kLeft, // Horizontal Swing + false, // Turbo + true, // Quiet + false, // Light + true, // Filter + false, // Clean + 8 * 60 + 0, // Sleep time + 17 * 60 + 31); // Clock + + ASSERT_EQ(expected_KKG9AC1, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(MIRAGE, ac._irsend.capture.decode_type); + ASSERT_EQ(kMirageBits, ac._irsend.capture.bits); + ASSERT_EQ(expected_KKG9AC1, IRAcUtils::resultAcToString(&ac._irsend.capture)); + ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); - ASSERT_EQ(expected, ac.toString()); + const char expected_KKG29AC1[] = + "Model: 2 (KKG29AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " + "Fan: 2 (Medium), Turbo: Off, Light: Off, Sleep: On, " + "Swing(V): On, Swing(H): On, Filter: On, Clean: -, " + "On Timer: Off, Off Timer: Off, IFeel: Off"; + ac._irsend.reset(); + irac.mirage(&ac, + mirage_ac_remote_model_t::KKG29AC1, // Model + true, // Power + stdAc::opmode_t::kDry, // Mode + 27, // Degrees (Celsius) + stdAc::fanspeed_t::kMedium, // Fan speed + stdAc::swingv_t::kHigh, // Veritical Swing + stdAc::swingh_t::kLeft, // Horizontal Swing + false, // Turbo + true, // Quiet + false, // Light + true, // Filter + false, // Clean + 8 * 60 + 0, // Sleep time + 17 * 60 + 31); // Clock + ASSERT_EQ(expected_KKG29AC1, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); ASSERT_EQ(MIRAGE, ac._irsend.capture.decode_type); ASSERT_EQ(kMirageBits, ac._irsend.capture.bits); - ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); - stdAc::state_t r, p; + ASSERT_EQ(expected_KKG29AC1, + IRAcUtils::resultAcToString(&ac._irsend.capture)); ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); } diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index b21f16bee..fa8c381af 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -56,9 +56,9 @@ TEST(TestDecodeMirage, RealExample) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " - "Swing(V): 0 (UNKNOWN), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -78,9 +78,9 @@ TEST(TestDecodeMirage, SyntheticExample) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " - "Swing(V): 0 (UNKNOWN), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -123,9 +123,9 @@ TEST(TestDecodeMirage, RealExampleWithDodgyHardwareCapture) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " - "Swing(V): 0 (UNKNOWN), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -182,10 +182,12 @@ TEST(TestMirageAcClass, OperatingMode) { TEST(TestMirageAcClass, HumanReadable) { IRMirageAc ac(kGpioUnused); ac.begin(); + + // Tests for the KKG9AC1 model. EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 16C, Fan: 0 (Auto), " - "Swing(V): 13 (Auto), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 00:00", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 16C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 13 (Auto), Clock: 00:00", ac.toString()); // Ref: https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0&range=C7 // 0x56710000201A00000C000C26010041 @@ -194,9 +196,9 @@ TEST(TestMirageAcClass, HumanReadable) { 0x0C, 0x00, 0x0C, 0x26, 0x01, 0x00, 0x41}; ac.setRaw(cool_21c_auto); EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 21C, Fan: 0 (Auto), " - "Swing(V): 13 (Auto), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 00:01", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 21C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 13 (Auto), Clock: 00:01", ac.toString()); const uint8_t SyntheticExample[kMirageStateLength] = { @@ -204,9 +206,19 @@ TEST(TestMirageAcClass, HumanReadable) { 0x00, 0x00, 0x16, 0x14, 0x26}; ac.setRaw(SyntheticExample); EXPECT_EQ( - "Power: On, Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " - "Swing(V): 0 (UNKNOWN), " - "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", + "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): 0 (Off), Clock: 14:16", + ac.toString()); + + // Tests for the KKG29AC1 model. + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); + EXPECT_EQ( + "Model: 2 (KKG29AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " + "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Swing(V): Off, Swing(H): Off, " + "Filter: Off, Clean: -, On Timer: Off, Off Timer: Off, " + "IFeel: Off", ac.toString()); } @@ -342,3 +354,18 @@ TEST(TestMirageAcClass, SwingV) { ac.setSwingV(kMirageAcSwingVLowest - 1); EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); } + +TEST(TestMirageAcClass, getModel) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + const uint8_t KKG9AC1[kMirageStateLength] = { + 0x56, 0x6C, 0x00, 0x00, 0x20, 0xD8, 0x00, 0x00, + 0x0C, 0x32, 0x0B, 0x00, 0x32, 0x0F, 0x64}; + EXPECT_EQ(mirage_ac_remote_model_t::KKG9AC1, IRMirageAc::getModel(KKG9AC1)); + + // https://github.com/crankyoldgit/IRremoteESP8266/issues/1573#issuecomment-955722044 + const uint8_t KKG29AC1[kMirageStateLength] = { + 0x56, 0x74, 0x00, 0x00, 0x12, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D}; + EXPECT_EQ(mirage_ac_remote_model_t::KKG29AC1, IRMirageAc::getModel(KKG29AC1)); +} From 5b85d027a7b158f6f864d0c56fef8308e5944285 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Tue, 2 Nov 2021 13:27:29 +1000 Subject: [PATCH 2/7] KKG29AC1 model improvements. * Tweak model detection in `getModel(state)` * Various improvements/fixes found in testing. * Update unit tests for `KKG29AC1` model. - Clock - Sleep - Light - Turbo - Power - SwingV * New unit tests for `KKG29AC1` - SwingH - Filter - Quiet - CleanToggle - Timers - IFeel & SensorTemp For #1573 --- src/ir_Mirage.cpp | 38 ++++--- test/ir_Mirage_test.cpp | 213 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 13 deletions(-) diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index a4f988677..08835999e 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -117,6 +117,7 @@ void IRMirageAc::begin(void) { _irsend.begin(); } /// @param[in] repeat Nr. of times the message will be repeated. void IRMirageAc::send(const uint16_t repeat) { _irsend.sendMirage(getRaw(), kMirageStateLength, repeat); + setCleanToggle(false); // Clean Toggle is reset after each send. } #endif // SEND_MITSUBISHI_AC @@ -138,17 +139,19 @@ void IRMirageAc::setRaw(const uint8_t *data) { /// @param[in] state A valid state code for this protocol. /// @return The model code. mirage_ac_remote_model_t IRMirageAc::getModel(const uint8_t *state) { - Mirage120Protocol prot; - std::memcpy(prot.raw, state, kMirageStateLength); - // If the minutes or seconds or raw[10] are set, it's a KKG9AC1 - if (prot.Minutes || prot.Seconds || prot.raw[10]) + Mirage120Protocol p; + std::memcpy(p.raw, state, kMirageStateLength); + // Check for things specific to KKG9AC1 + if ((p.Minutes || p.Seconds) && // Is part of the clock set? + // Are the timer times set, but not enabled? + (!p.OffTimerEnable && (p.OffTimerHours || p.OffTimerMins)) && + (!p.OnTimerEnable && (p.OnTimerHours || p.OnTimerMins))) return mirage_ac_remote_model_t::KKG9AC1; // Check for KKG29AC1 specific settings. - if (prot.RecycleHeat || prot.Filter || prot.Sleep_Kkg29ac1 || - prot.CleanToggle || prot.IFeel) + if (p.RecycleHeat || p.Filter || p.Sleep_Kkg29ac1 || p.CleanToggle || + p.IFeel || p.OffTimerEnable || p.OnTimerEnable) return mirage_ac_remote_model_t::KKG29AC1; - else - return mirage_ac_remote_model_t::KKG9AC1; + return mirage_ac_remote_model_t::KKG9AC1; // Default. } /// Get the model code of the interal message state. @@ -163,9 +166,11 @@ mirage_ac_remote_model_t IRMirageAc::getModel(const bool useRaw) const { void IRMirageAc::setModel(mirage_ac_remote_model_t model) { if (model != _model) { // Only change things if we need to. // Save the old settings. - stdAc::state_t old = toCommon(); - uint16_t ontimer = getOnTimer(); - uint16_t offtimer = getOffTimer(); + const stdAc::state_t old = toCommon(); + const uint16_t ontimer = getOnTimer(); + const uint16_t offtimer = getOffTimer(); + const bool ifeel = getIFeel(); + const uint8_t sensor = getSensorTemp(); // Change the model. _model = model; // Restore/Convert the settings. @@ -184,6 +189,8 @@ void IRMirageAc::setModel(mirage_ac_remote_model_t model) { setClock(old.clock * 60); // setClock() expects seconds, not minutes. setOnTimer(ontimer); setOffTimer(offtimer); + setIFeel(ifeel); + setSensorTemp(sensor); } } @@ -417,7 +424,7 @@ void IRMirageAc::setSwingV(const uint8_t position) { uint8_t IRMirageAc::getSwingV(void) const { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: - return _.SwingV; + return _.SwingV ? kMirageAcSwingVAuto : kMirageAcSwingVOff; default: return _.SwingAndPower - (getPower() ? 0 : kMirageAcPowerOff); } @@ -513,7 +520,12 @@ void IRMirageAc::setIFeel(const bool on) { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: _.IFeel = on; - if (!on) _.SensorTemp = 0; + if (on) { + // If no previous sensor temp, default to currently desired temp. + if (!_.SensorTemp) _.SensorTemp = getTemp(); + } else { + _.SensorTemp = 0; // When turning it off, clear the Sensor Temp. + } break; default: break; diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index fa8c381af..93aba68b1 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -133,6 +133,7 @@ TEST(TestMirageAcClass, Power) { IRMirageAc ac(kGpioUnused); ac.begin(); + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); ac.on(); EXPECT_TRUE(ac.getPower()); ac.on(); @@ -159,6 +160,16 @@ TEST(TestMirageAcClass, Power) { 0x0C, 0x00, 0x0C, 0x2C, 0x23, 0x01, 0x61}; ac.setRaw(off); EXPECT_FALSE(ac.getPower()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); + ac.on(); + EXPECT_TRUE(ac.getPower()); + ac.off(); + EXPECT_FALSE(ac.getPower()); + ac.setPower(true); + EXPECT_TRUE(ac.getPower()); + ac.setPower(false); + EXPECT_FALSE(ac.getPower()); } TEST(TestMirageAcClass, OperatingMode) { @@ -278,6 +289,15 @@ TEST(TestMirageAcClass, Turbo) { IRMirageAc ac(kGpioUnused); ac.begin(); + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); ac.setTurbo(true); EXPECT_TRUE(ac.getTurbo()); ac.setTurbo(false); @@ -290,6 +310,15 @@ TEST(TestMirageAcClass, Light) { IRMirageAc ac(kGpioUnused); ac.begin(); + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_FALSE(ac.getLight()); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); ac.setLight(true); EXPECT_TRUE(ac.getLight()); ac.setLight(false); @@ -302,6 +331,15 @@ TEST(TestMirageAcClass, Sleep) { IRMirageAc ac(kGpioUnused); ac.begin(); + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); ac.setSleep(true); EXPECT_TRUE(ac.getSleep()); ac.setSleep(false); @@ -314,6 +352,7 @@ TEST(TestMirageAcClass, Clock) { IRMirageAc ac(kGpioUnused); ac.begin(); + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // This model supports time. ac.setClock(0); EXPECT_EQ(0, ac.getClock()); ac.setClock(12 * 60 * 60 + 30 * 60 + 59); // aka. 12:30:59 @@ -322,6 +361,11 @@ TEST(TestMirageAcClass, Clock) { EXPECT_EQ(23 * 60 * 60 + 59 * 60 + 59, ac.getClock()); ac.setClock(24 * 60 * 60); // aka. 24:00:00 EXPECT_EQ(23 * 60 * 60 + 59 * 60 + 59, ac.getClock()); // aka. 23:59:59 + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); // This model has no clock. + EXPECT_EQ(0, ac.getClock()); + ac.setClock(12 * 60 * 60 + 30 * 60 + 59); // aka. 12:30:59 + EXPECT_EQ(0, ac.getClock()); } TEST(TestMirageAcClass, Checksums) { @@ -339,6 +383,9 @@ TEST(TestMirageAcClass, SwingV) { IRMirageAc ac(kGpioUnused); ac.begin(); + // Set the model to one with full swingv support. + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setSwingV(kMirageAcSwingVAuto); EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); @@ -353,6 +400,172 @@ TEST(TestMirageAcClass, SwingV) { ac.setSwingV(kMirageAcSwingVLowest - 1); EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); + + // Set the model to one with limited swingv support. + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); + + ac.setSwingV(kMirageAcSwingVAuto); + EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); + ac.setSwingV(kMirageAcSwingVOff); + EXPECT_EQ(kMirageAcSwingVOff, ac.getSwingV()); + ac.setSwingV(kMirageAcSwingVHigh); + EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); + ac.setSwingV(0xFF); + EXPECT_EQ(kMirageAcSwingVAuto, ac.getSwingV()); + ac.setSwingV(kMirageAcSwingVOff); + EXPECT_EQ(kMirageAcSwingVOff, ac.getSwingV()); +} + +TEST(TestMirageAcClass, SwingH) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setSwingH(true); + EXPECT_FALSE(ac.getSwingH()); + ac.setSwingH(false); + EXPECT_FALSE(ac.getSwingH()); + ac.setSwingH(true); + EXPECT_FALSE(ac.getSwingH()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); + ac.setSwingH(true); + EXPECT_TRUE(ac.getSwingH()); + ac.setSwingH(false); + EXPECT_FALSE(ac.getSwingH()); + ac.setSwingH(true); + EXPECT_TRUE(ac.getSwingH()); +} + +TEST(TestMirageAcClass, Filter) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // No Support + ac.setFilter(true); + EXPECT_FALSE(ac.getFilter()); + ac.setFilter(false); + EXPECT_FALSE(ac.getFilter()); + ac.setFilter(true); + EXPECT_FALSE(ac.getFilter()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); // Supported + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); + ac.setFilter(false); + EXPECT_FALSE(ac.getFilter()); + ac.setFilter(true); + EXPECT_TRUE(ac.getFilter()); +} + +TEST(TestMirageAcClass, Quiet) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // No Support + ac.setQuiet(true); + EXPECT_FALSE(ac.getQuiet()); + ac.setQuiet(false); + EXPECT_FALSE(ac.getQuiet()); + ac.setQuiet(true); + EXPECT_FALSE(ac.getQuiet()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); // Supported + ac.setQuiet(true); + EXPECT_TRUE(ac.getQuiet()); + ac.setQuiet(false); + EXPECT_FALSE(ac.getQuiet()); + ac.setQuiet(true); + EXPECT_TRUE(ac.getQuiet()); +} + +TEST(TestMirageAcClass, CleanToggle) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); + ac.setCleanToggle(true); + EXPECT_FALSE(ac.getCleanToggle()); + ac.setCleanToggle(false); + EXPECT_FALSE(ac.getCleanToggle()); + ac.setCleanToggle(true); + EXPECT_FALSE(ac.getCleanToggle()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); + ac.setCleanToggle(true); + EXPECT_TRUE(ac.getCleanToggle()); + ac.setCleanToggle(false); + EXPECT_FALSE(ac.getCleanToggle()); + ac.setCleanToggle(true); + EXPECT_TRUE(ac.getCleanToggle()); + ac.send(); // Should be reset when sent. + EXPECT_FALSE(ac.getCleanToggle()); +} + +TEST(TestMirageAcClass, Timers) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // No timer support + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + ac.setOnTimer(12 * 60 + 37); // 12:37 + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + ac.setOffTimer(17 * 60 + 5); // 17:05 + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); // Timer supported + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOnTimer(12 * 60 + 37); // 12:37 + EXPECT_EQ(12 * 60 + 37, ac.getOnTimer()); + EXPECT_EQ(0, ac.getOffTimer()); + + ac.setOffTimer(17 * 60 + 5); // 17:05 + EXPECT_EQ(17 * 60 + 5, ac.getOffTimer()); + EXPECT_EQ(12 * 60 + 37, ac.getOnTimer()); + ac.setOnTimer(0); // Off/Disabled + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ(17 * 60 + 5, ac.getOffTimer()); + ac.setOffTimer(0); // Off/Disabled + EXPECT_EQ(0, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); + + ac.setOnTimer(12 * 60 + 37); // 12:37 + ac.setOffTimer(17 * 60 + 5); // 17:05 + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // No timer support + EXPECT_EQ(0, ac.getOffTimer()); + EXPECT_EQ(0, ac.getOnTimer()); +} + +TEST(TestMirageAcClass, IFeelAndSensorTemp) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setModel(mirage_ac_remote_model_t::KKG9AC1); // No support + EXPECT_FALSE(ac.getIFeel()); + EXPECT_EQ(0, ac.getSensorTemp()); + ac.setIFeel(true); + EXPECT_FALSE(ac.getIFeel()); + EXPECT_EQ(0, ac.getSensorTemp()); + ac.setSensorTemp(20); // 20C + EXPECT_FALSE(ac.getIFeel()); + EXPECT_EQ(0, ac.getSensorTemp()); + + ac.setModel(mirage_ac_remote_model_t::KKG29AC1); // Supported + EXPECT_FALSE(ac.getIFeel()); + EXPECT_EQ(0, ac.getSensorTemp()); + ac.setIFeel(true); + EXPECT_TRUE(ac.getIFeel()); + EXPECT_EQ(0, ac.getSensorTemp()); + ac.setSensorTemp(25); // 25C + EXPECT_TRUE(ac.getIFeel()); + EXPECT_EQ(25, ac.getSensorTemp()); + ac.setIFeel(false); + EXPECT_FALSE(ac.getIFeel()); } TEST(TestMirageAcClass, getModel) { From ecc60cc961e87a164a6c5588aa5dd87fbe1f7582 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Wed, 3 Nov 2021 07:26:26 +1000 Subject: [PATCH 3/7] Improve KKG29AC1 support based on user feedback. * Fix power state for KKG29AC1 * Make light a toggle for KKG29AC1. * Use different bits for Fan Low & Medium. For #1573 --- src/IRac.cpp | 4 +- src/ir_Mirage.cpp | 82 ++++++++++++++++++++++++++++++++--------- src/ir_Mirage.h | 10 ++++- test/IRac_test.cpp | 4 +- test/ir_Mirage_test.cpp | 14 +++---- 5 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index f8df60b1d..0d3cd6330 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1508,7 +1508,7 @@ void IRac::mirage(IRMirageAc *ac, const mirage_ac_remote_model_t model, ac->setPower(on); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); - ac->setFan(ac->convertFan(fan)); + ac->setFan(ac->convertFan(fan, model)); ac->setSwingV(ac->convertSwingV(swingv)); ac->setSwingH(swingh != stdAc::swingh_t::kOff); ac->setTurbo(turbo); @@ -2532,6 +2532,8 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, result.power = desired.power ^ prev->power; break; case decode_type_t::MIRAGE: + if (desired.model == mirage_ac_remote_model_t::KKG29AC1) + result.light = desired.light ^ prev->light; result.clean = desired.clean ^ prev->clean; break; case decode_type_t::PANASONIC_AC: diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index 08835999e..3332117fd 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -117,7 +117,15 @@ void IRMirageAc::begin(void) { _irsend.begin(); } /// @param[in] repeat Nr. of times the message will be repeated. void IRMirageAc::send(const uint16_t repeat) { _irsend.sendMirage(getRaw(), kMirageStateLength, repeat); - setCleanToggle(false); // Clean Toggle is reset after each send. + // Reset any toggles after a send. + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + setCleanToggle(false); + setLight(false); // For this model (only), Light is a toggle. + break; + default: + break; + } } #endif // SEND_MITSUBISHI_AC @@ -222,7 +230,7 @@ void IRMirageAc::off(void) { setPower(false); } void IRMirageAc::setPower(bool on) { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: - _.Power = on ? 0b11 : 0b00; + _.Power = on ? 0b00 : 0b11; break; default: // In order to change the power setting, it seems must be less than @@ -248,7 +256,7 @@ void IRMirageAc::setPower(bool on) { bool IRMirageAc::getPower(void) const { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: - return _.Power == 0b11; + return _.Power == 0b00; default: return _.SwingAndPower < kMirageAcPowerOff; } @@ -343,6 +351,7 @@ bool IRMirageAc::getSleep(void) const { /// Change the Light/Display setting. /// @param[in] on true, the setting is on. false, the setting is off. +/// @note Light is a toggle on the KKG29AC1 model. void IRMirageAc::setLight(bool on) { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: @@ -355,6 +364,7 @@ void IRMirageAc::setLight(bool on) { /// Get the value of the current Light/Display setting. /// @return true, the setting is on. false, the setting is off. +/// @note Light is a toggle on the KKG29AC1 model. bool IRMirageAc::getLight(void) const { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: return _.LightToggle_Kkg29ac1; @@ -631,13 +641,26 @@ stdAc::opmode_t IRMirageAc::toCommonMode(const uint8_t mode) { /// Convert a native fan speed into its stdAc equivalent. /// @param[in] speed The native setting to be converted. +/// @param[in] model The model type to use to influence the conversion. /// @return The stdAc equivalent of the native setting. -stdAc::fanspeed_t IRMirageAc::toCommonFanSpeed(const uint8_t speed) { - switch (speed) { - case kMirageAcFanHigh: return stdAc::fanspeed_t::kHigh; - case kMirageAcFanMed: return stdAc::fanspeed_t::kMedium; - case kMirageAcFanLow: return stdAc::fanspeed_t::kLow; - default: return stdAc::fanspeed_t::kAuto; +stdAc::fanspeed_t IRMirageAc::toCommonFanSpeed(const uint8_t speed, + const mirage_ac_remote_model_t model) { + switch (model) { + case mirage_ac_remote_model_t::KKG29AC1: + switch (speed) { + case kMirageAcKKG29AC1FanHigh: return stdAc::fanspeed_t::kHigh; + case kMirageAcKKG29AC1FanMed: return stdAc::fanspeed_t::kMedium; + case kMirageAcKKG29AC1FanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } + break; + default: + switch (speed) { + case kMirageAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kMirageAcFanMed: return stdAc::fanspeed_t::kMedium; + case kMirageAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } } } @@ -655,12 +678,25 @@ uint8_t IRMirageAc::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. +/// @param[in] model The model type to use to influence the conversion. /// @return The native equivalent of the enum. -uint8_t IRMirageAc::convertFan(const stdAc::fanspeed_t speed) { +uint8_t IRMirageAc::convertFan(const stdAc::fanspeed_t speed, + const mirage_ac_remote_model_t model) { + uint8_t low; + uint8_t med; + switch (model) { + case mirage_ac_remote_model_t::KKG29AC1: + low = kMirageAcKKG29AC1FanLow; + med = kMirageAcKKG29AC1FanMed; + break; + default: + low = kMirageAcFanLow; + med = kMirageAcFanMed; + } switch (speed) { case stdAc::fanspeed_t::kMin: - case stdAc::fanspeed_t::kLow: return kMirageAcFanLow; - case stdAc::fanspeed_t::kMedium: return kMirageAcFanMed; + case stdAc::fanspeed_t::kLow: return low; + case stdAc::fanspeed_t::kMedium: return med; case stdAc::fanspeed_t::kHigh: case stdAc::fanspeed_t::kMax: return kMirageAcFanHigh; default: return kMirageAcFanAuto; @@ -707,7 +743,7 @@ stdAc::state_t IRMirageAc::toCommon(void) const { result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); - result.fanspeed = toCommonFanSpeed(getFan()); + result.fanspeed = toCommonFanSpeed(getFan(), _model); result.swingv = toCommonSwingV(getSwingV()); result.swingh = getSwingH() ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; result.turbo = getTurbo(); @@ -734,15 +770,24 @@ String IRMirageAc::toString(void) const { kMirageAcHeat, kMirageAcDry, kMirageAcFan); result += addTempToString(getTemp()); - result += addFanToString(_.Fan, kMirageAcFanHigh, - kMirageAcFanLow, - kMirageAcFanAuto, kMirageAcFanAuto, - kMirageAcFanMed); + uint8_t fanlow; + uint8_t fanmed; + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + fanlow = kMirageAcKKG29AC1FanLow; + fanmed = kMirageAcKKG29AC1FanMed; + break; + default: + fanlow = kMirageAcFanLow; + fanmed = kMirageAcFanMed; + } + result += addFanToString(_.Fan, kMirageAcFanHigh, fanlow, kMirageAcFanAuto, + kMirageAcFanAuto, fanmed); result += addBoolToString(getTurbo(), kTurboStr); - result += addBoolToString(getLight(), kLightStr); result += addBoolToString(getSleep(), kSleepStr); switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: + result += addToggleToString(getLight(), kLightStr); result += addBoolToString(_.SwingV, kSwingVStr); result += addBoolToString(_.SwingH, kSwingHStr); result += addBoolToString(_.Filter, kFilterStr); @@ -760,6 +805,7 @@ String IRMirageAc::toString(void) const { } break; default: + result += addBoolToString(getLight(), kLightStr); result += addSwingVToString(getSwingV(), kMirageAcSwingVAuto, kMirageAcSwingVHighest, diff --git a/src/ir_Mirage.h b/src/ir_Mirage.h index e999fad70..f8b2077d8 100644 --- a/src/ir_Mirage.h +++ b/src/ir_Mirage.h @@ -169,6 +169,10 @@ const uint8_t kMirageAcFanAuto = 0b00; // 0 const uint8_t kMirageAcFanHigh = 0b01; // 1 const uint8_t kMirageAcFanMed = 0b10; // 2 const uint8_t kMirageAcFanLow = 0b11; // 3 +const uint8_t kMirageAcKKG29AC1FanAuto = 0b00; // 0 +const uint8_t kMirageAcKKG29AC1FanHigh = 0b01; // 1 +const uint8_t kMirageAcKKG29AC1FanLow = 0b10; // 2 +const uint8_t kMirageAcKKG29AC1FanMed = 0b11; // 3 const uint8_t kMirageAcMinTemp = 16; // 16C const uint8_t kMirageAcMaxTemp = 32; // 32C @@ -247,10 +251,12 @@ class IRMirageAc { static bool validChecksum(const uint8_t* data); static uint8_t calculateChecksum(const uint8_t* data); static uint8_t convertMode(const stdAc::opmode_t mode); - static uint8_t convertFan(const stdAc::fanspeed_t speed); + static uint8_t convertFan(const stdAc::fanspeed_t speed, + const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1); static uint8_t convertSwingV(const stdAc::swingv_t position); static stdAc::opmode_t toCommonMode(const uint8_t mode); - static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed, + const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1); static stdAc::swingv_t toCommonSwingV(const uint8_t pos); stdAc::state_t toCommon(void) const; String toString(void) const; diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 69a4f861a..a43d463f1 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1355,7 +1355,7 @@ TEST(TestIRac, Mirage) { stdAc::state_t r, p; const char expected_KKG9AC1[] = "Model: 1 (KKG9AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " - "Fan: 2 (Medium), Turbo: Off, Light: Off, Sleep: On, " + "Fan: 2 (Medium), Turbo: Off, Sleep: On, Light: Off, " "Swing(V): 9 (High), Clock: 17:31"; ac.begin(); @@ -1385,7 +1385,7 @@ TEST(TestIRac, Mirage) { const char expected_KKG29AC1[] = "Model: 2 (KKG29AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " - "Fan: 2 (Medium), Turbo: Off, Light: Off, Sleep: On, " + "Fan: 3 (Medium), Turbo: Off, Sleep: On, Light: -, " "Swing(V): On, Swing(H): On, Filter: On, Clean: -, " "On Timer: Off, Off Timer: Off, IFeel: Off"; ac._irsend.reset(); diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index 93aba68b1..c63b7b1fd 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -57,7 +57,7 @@ TEST(TestDecodeMirage, RealExample) { EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -79,7 +79,7 @@ TEST(TestDecodeMirage, SyntheticExample) { EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -124,7 +124,7 @@ TEST(TestDecodeMirage, RealExampleWithDodgyHardwareCapture) { EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -197,7 +197,7 @@ TEST(TestMirageAcClass, HumanReadable) { // Tests for the KKG9AC1 model. EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 16C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 13 (Auto), Clock: 00:00", ac.toString()); // Ref: https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0&range=C7 @@ -208,7 +208,7 @@ TEST(TestMirageAcClass, HumanReadable) { ac.setRaw(cool_21c_auto); EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 21C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 13 (Auto), Clock: 00:01", ac.toString()); @@ -218,7 +218,7 @@ TEST(TestMirageAcClass, HumanReadable) { ac.setRaw(SyntheticExample); EXPECT_EQ( "Model: 1 (KKG9AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: Off, " "Swing(V): 0 (Off), Clock: 14:16", ac.toString()); @@ -226,7 +226,7 @@ TEST(TestMirageAcClass, HumanReadable) { ac.setModel(mirage_ac_remote_model_t::KKG29AC1); EXPECT_EQ( "Model: 2 (KKG29AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Light: Off, Sleep: Off, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: -, " "Swing(V): Off, Swing(H): Off, " "Filter: Off, Clean: -, On Timer: Off, Off Timer: Off, " "IFeel: Off", From 92f57b03da330035e8f63b982d56b09ca9a2cda1 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Wed, 3 Nov 2021 15:36:19 +1000 Subject: [PATCH 4/7] Use constants instead of literals for KKG29AC1 power control. --- src/ir_Mirage.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index 3332117fd..1dcf699da 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -40,6 +40,9 @@ const uint16_t kMirageZeroSpace = 545; ///< uSeconds const uint32_t kMirageGap = kDefaultMessageGap; ///< uSeconds (just a guess) const uint16_t kMirageFreq = 38000; ///< Hz. (Just a guess) +const uint8_t kMirageAcKKG29AC1PowerOn = 0b00; // 0 +const uint8_t kMirageAcKKG29AC1PowerOff = 0b11; // 3 + #if SEND_MIRAGE /// Send a Mirage formatted message. @@ -230,7 +233,7 @@ void IRMirageAc::off(void) { setPower(false); } void IRMirageAc::setPower(bool on) { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: - _.Power = on ? 0b00 : 0b11; + _.Power = on ? kMirageAcKKG29AC1PowerOn : kMirageAcKKG29AC1PowerOff; break; default: // In order to change the power setting, it seems must be less than @@ -256,7 +259,7 @@ void IRMirageAc::setPower(bool on) { bool IRMirageAc::getPower(void) const { switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: - return _.Power == 0b00; + return _.Power == kMirageAcKKG29AC1PowerOn; default: return _.SwingAndPower < kMirageAcPowerOff; } From 7bb218df4f6f11c6adad77bc5d8d434a52965407 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Wed, 3 Nov 2021 15:38:45 +1000 Subject: [PATCH 5/7] Update supported AC model. --- src/ir_Mirage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir_Mirage.h b/src/ir_Mirage.h index f8b2077d8..dc89d97af 100644 --- a/src/ir_Mirage.h +++ b/src/ir_Mirage.h @@ -9,7 +9,7 @@ // Brand: Mirage, Model: VLU series A/C // Brand: Maxell, Model: MX-CH18CF A/C // Brand: Maxell, Model: KKG9A-C1 remote -// Brand: Tronitechnik, Model: A/C +// Brand: Tronitechnik, Model: Reykir 9000 A/C // Brand: Tronitechnik, Model: KKG29A-C1 remote #ifndef IR_MIRAGE_H_ From be51b41d0b1c838ae717bb52ad08e182d6613e1c Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Thu, 4 Nov 2021 13:32:40 +1000 Subject: [PATCH 6/7] KKG29AC1 improvements. * Based on real-world data/experience/feedback. * Add quiet to `toString()` * Improve model detection (hopefully) * Reduce code duplication between `IRMirageAc` & `IRac` using new `fromCommon()` method. * make `setModel()`s argument a `const`. * Update/adjust unit tests. For #1573 --- src/IRac.cpp | 45 +++----------------------- src/IRac.h | 8 +---- src/ir_Mirage.cpp | 70 ++++++++++++++++++++++++++--------------- src/ir_Mirage.h | 3 +- test/IRac_test.cpp | 54 +++++++++++++------------------ test/ir_Mirage_test.cpp | 2 +- 6 files changed, 75 insertions(+), 107 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 0d3cd6330..ef6b7bf49 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1481,44 +1481,10 @@ void IRac::midea(IRMideaAC *ac, #if SEND_MIRAGE /// Send a Mirage 120-bit A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. -/// @param[in] model The A/C model 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] swingh The horizontal swing setting. -/// @param[in] turbo Run the device in turbo mode. -/// @param[in] quiet Run the device in quiet/silent mode. -/// @param[in] light Turn on the Light/Display. -/// @param[in] filter Turn on the (UVC/ion/pollen/etc) filter mode. -/// @param[in] clean Turn on the self-cleaning mode. e.g. XFan, dry filters etc -/// @param[in] sleep The time in Nr. of mins to sleep for. < 0 is ignore. -/// @note Sleep is either on or off. The time is useless. -/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. -void IRac::mirage(IRMirageAc *ac, const mirage_ac_remote_model_t model, - const bool on, const stdAc::opmode_t mode, - const float degrees, const stdAc::fanspeed_t fan, - const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool quiet, const bool light, - const bool filter, const bool clean, - const int16_t sleep, const int16_t clock) { +/// @param[in] state The desired state to send. +void IRac::mirage(IRMirageAc *ac, const stdAc::state_t state) { ac->begin(); - ac->setModel(model); - ac->setPower(on); - ac->setMode(ac->convertMode(mode)); - ac->setTemp(degrees); - ac->setFan(ac->convertFan(fan, model)); - ac->setSwingV(ac->convertSwingV(swingv)); - ac->setSwingH(swingh != stdAc::swingh_t::kOff); - ac->setTurbo(turbo); - ac->setQuiet(quiet); - ac->setLight(light); - ac->setFilter(filter); - ac->setCleanToggle(clean); - // No Beep setting available. - ac->setSleep(sleep >= 0); - if (clock >= 0) ac->setClock(clock * 60); // Clock is in seconds. + ac->fromCommon(state); ac->send(); } #endif // SEND_MIRAGE @@ -2907,10 +2873,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { case MIRAGE: { IRMirageAc ac(_pin, _inverted, _modulation); - mirage(&ac, (mirage_ac_remote_model_t)send.model, send.power, send.mode, - degC, send.fanspeed, send.swingv, send.swingh, - send.turbo, send.quiet, send.light, send.filter, send.clean, - send.sleep, send.clock); + mirage(&ac, send); break; } #endif // SEND_MIRAGE diff --git a/src/IRac.h b/src/IRac.h index 84f179b51..d4e3ed889 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -330,13 +330,7 @@ void electra(IRElectraAc *ac, const bool light, const int16_t sleep = -1); #endif // SEND_MIDEA #if SEND_MIRAGE - void mirage(IRMirageAc *ac, const mirage_ac_remote_model_t model, - const bool on, const stdAc::opmode_t mode, - const float degrees, const stdAc::fanspeed_t fan, - const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool quiet, const bool light, - const bool filter, const bool clean, - const int16_t sleep = -1, const int16_t clock = -1); + void mirage(IRMirageAc *ac, const stdAc::state_t state); #endif // SEND_MIRAGE #if SEND_MITSUBISHI_AC void mitsubishi(IRMitsubishiAC *ac, diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index 1dcf699da..a806937b1 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -110,6 +110,7 @@ void IRMirageAc::stateReset(void) { 0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x42}; setRaw(kReset); + _model = mirage_ac_remote_model_t::KKG9AC1; } /// Set up hardware to be able to send a message. @@ -149,20 +150,25 @@ void IRMirageAc::setRaw(const uint8_t *data) { /// Guess the Mirage remote model from the supplied state code. /// @param[in] state A valid state code for this protocol. /// @return The model code. +/// @note This result isn't perfect. Both protocols can look the same but have +/// wildly different settings. mirage_ac_remote_model_t IRMirageAc::getModel(const uint8_t *state) { Mirage120Protocol p; std::memcpy(p.raw, state, kMirageStateLength); - // Check for things specific to KKG9AC1 - if ((p.Minutes || p.Seconds) && // Is part of the clock set? - // Are the timer times set, but not enabled? - (!p.OffTimerEnable && (p.OffTimerHours || p.OffTimerMins)) && - (!p.OnTimerEnable && (p.OnTimerHours || p.OnTimerMins))) - return mirage_ac_remote_model_t::KKG9AC1; // Check for KKG29AC1 specific settings. if (p.RecycleHeat || p.Filter || p.Sleep_Kkg29ac1 || p.CleanToggle || p.IFeel || p.OffTimerEnable || p.OnTimerEnable) return mirage_ac_remote_model_t::KKG29AC1; - return mirage_ac_remote_model_t::KKG9AC1; // Default. + // Check for things specific to KKG9AC1 + if ((p.Minutes || p.Seconds) || // Is part of the clock set? + // Are the timer times set, but not enabled? (enable check filtered above) + (p.OffTimerHours || p.OffTimerMins) || + (p.OnTimerHours || p.OnTimerMins)) + return mirage_ac_remote_model_t::KKG9AC1; + // As the above test has a 1 in 3600+ (for 1 second an hour) chance of a false + // negative in theory, we are going assume that anything left should be a + // KKG29AC1 model. + return mirage_ac_remote_model_t::KKG29AC1; // Default. } /// Get the model code of the interal message state. @@ -174,30 +180,18 @@ mirage_ac_remote_model_t IRMirageAc::getModel(const bool useRaw) const { /// Set the model code of the interal message state. /// @param[in] model The desired model to use for the settings. -void IRMirageAc::setModel(mirage_ac_remote_model_t model) { +void IRMirageAc::setModel(const mirage_ac_remote_model_t model) { if (model != _model) { // Only change things if we need to. // Save the old settings. - const stdAc::state_t old = toCommon(); + stdAc::state_t state = toCommon(); const uint16_t ontimer = getOnTimer(); const uint16_t offtimer = getOffTimer(); const bool ifeel = getIFeel(); const uint8_t sensor = getSensorTemp(); // Change the model. - _model = model; + state.model = model; // Restore/Convert the settings. - setPower(old.power); - setTemp(old.degrees); - setMode(convertMode(old.mode)); - setFan(convertFan(old.fanspeed)); - setTurbo(old.turbo); - setSleep(old.sleep >= 0); - setLight(old.light); - setSwingV(convertSwingV(old.swingv)); - setSwingH(old.swingh != stdAc::swingh_t::kOff); - setQuiet(old.quiet); - setCleanToggle(old.clean); - setFilter(old.filter); - setClock(old.clock * 60); // setClock() expects seconds, not minutes. + fromCommon(state); setOnTimer(ontimer); setOffTimer(offtimer); setIFeel(ifeel); @@ -762,6 +756,31 @@ stdAc::state_t IRMirageAc::toCommon(void) const { return result; } +/// Convert & set a stdAc::state_t to its equivalent internal settings. +/// @param[in] state The desired state in stdAc::state_t form. +void IRMirageAc::fromCommon(const stdAc::state_t state) { + stateReset(); + _model = (mirage_ac_remote_model_t)state.model; // Set directly to avoid loop + setPower(state.power); + setTemp(state.celsius ? state.degrees : fahrenheitToCelsius(state.degrees)); + setMode(convertMode(state.mode)); + setFan(convertFan(state.fanspeed, _model)); + setTurbo(state.turbo); + setSleep(state.sleep >= 0); + setLight(state.light); + setSwingV(convertSwingV(state.swingv)); + setSwingH(state.swingh != stdAc::swingh_t::kOff); + setQuiet(state.quiet); + setCleanToggle(state.clean); + setFilter(state.filter); + // setClock() expects seconds, not minutes. + setClock((state.clock > 0) ? state.clock * 60 : 0); + // Non-common settings. + setOnTimer(0); + setOffTimer(0); + setIFeel(false); +} + /// Convert the internal state into a human readable string. /// @return A string containing the settings in human-readable form. String IRMirageAc::toString(void) const { @@ -780,7 +799,7 @@ String IRMirageAc::toString(void) const { fanlow = kMirageAcKKG29AC1FanLow; fanmed = kMirageAcKKG29AC1FanMed; break; - default: + default: // e.g. Model KKG9AC1 fanlow = kMirageAcFanLow; fanmed = kMirageAcFanMed; } @@ -790,6 +809,7 @@ String IRMirageAc::toString(void) const { result += addBoolToString(getSleep(), kSleepStr); switch (_model) { case mirage_ac_remote_model_t::KKG29AC1: + result += addBoolToString(_.Quiet, kQuietStr); result += addToggleToString(getLight(), kLightStr); result += addBoolToString(_.SwingV, kSwingVStr); result += addBoolToString(_.SwingH, kSwingHStr); @@ -807,7 +827,7 @@ String IRMirageAc::toString(void) const { result += 'C'; } break; - default: + default: // e.g. Model KKG9AC1 result += addBoolToString(getLight(), kLightStr); result += addSwingVToString(getSwingV(), kMirageAcSwingVAuto, diff --git a/src/ir_Mirage.h b/src/ir_Mirage.h index dc89d97af..b2b39e759 100644 --- a/src/ir_Mirage.h +++ b/src/ir_Mirage.h @@ -246,7 +246,7 @@ class IRMirageAc { void setOnTimer(const uint16_t nr_of_mins); void setOffTimer(const uint16_t nr_of_mins); mirage_ac_remote_model_t getModel(const bool useRaw = false) const; - void setModel(mirage_ac_remote_model_t model); + void setModel(const mirage_ac_remote_model_t model); static mirage_ac_remote_model_t getModel(const uint8_t *state); static bool validChecksum(const uint8_t* data); static uint8_t calculateChecksum(const uint8_t* data); @@ -259,6 +259,7 @@ class IRMirageAc { const mirage_ac_remote_model_t model = mirage_ac_remote_model_t::KKG9AC1); static stdAc::swingv_t toCommonSwingV(const uint8_t pos); stdAc::state_t toCommon(void) const; + void fromCommon(const stdAc::state_t state); String toString(void) const; #ifndef UNIT_TEST diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index a43d463f1..e0afc4257 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1352,28 +1352,31 @@ TEST(TestIRac, Mirage) { IRMirageAc ac(kGpioUnused); IRac irac(kGpioUnused); IRrecv capture(kGpioUnused); - stdAc::state_t r, p; + stdAc::state_t state, r, p; const char expected_KKG9AC1[] = "Model: 1 (KKG9AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " "Fan: 2 (Medium), Turbo: Off, Sleep: On, Light: Off, " "Swing(V): 9 (High), Clock: 17:31"; ac.begin(); - irac.mirage(&ac, - mirage_ac_remote_model_t::KKG9AC1, // Model - true, // Power - stdAc::opmode_t::kDry, // Mode - 27, // Degrees (Celsius) - stdAc::fanspeed_t::kMedium, // Fan speed - stdAc::swingv_t::kHigh, // Veritical Swing - stdAc::swingh_t::kLeft, // Horizontal Swing - false, // Turbo - true, // Quiet - false, // Light - true, // Filter - false, // Clean - 8 * 60 + 0, // Sleep time - 17 * 60 + 31); // Clock + + state.model = mirage_ac_remote_model_t::KKG9AC1; + state.power = true; + state.mode = stdAc::opmode_t::kDry; + state.celsius = true; + state.degrees = 27; + state.fanspeed = stdAc::fanspeed_t::kMedium; + state.swingv = stdAc::swingv_t::kHigh; + state.swingh = stdAc::swingh_t::kLeft; + state.turbo = false; + state.quiet = true; + state.light = false; + state.filter = true; + state.clean = false; + state.sleep = 8 * 60 + 0; + state.clock = 17 * 60 + 31; + state.beep = false; + irac.mirage(&ac, state); ASSERT_EQ(expected_KKG9AC1, ac.toString()); ac._irsend.makeDecodeResult(); @@ -1385,25 +1388,12 @@ TEST(TestIRac, Mirage) { const char expected_KKG29AC1[] = "Model: 2 (KKG29AC1), Power: On, Mode: 3 (Dry), Temp: 27C, " - "Fan: 3 (Medium), Turbo: Off, Sleep: On, Light: -, " + "Fan: 3 (Medium), Turbo: Off, Sleep: On, Quiet: On, Light: -, " "Swing(V): On, Swing(H): On, Filter: On, Clean: -, " "On Timer: Off, Off Timer: Off, IFeel: Off"; ac._irsend.reset(); - irac.mirage(&ac, - mirage_ac_remote_model_t::KKG29AC1, // Model - true, // Power - stdAc::opmode_t::kDry, // Mode - 27, // Degrees (Celsius) - stdAc::fanspeed_t::kMedium, // Fan speed - stdAc::swingv_t::kHigh, // Veritical Swing - stdAc::swingh_t::kLeft, // Horizontal Swing - false, // Turbo - true, // Quiet - false, // Light - true, // Filter - false, // Clean - 8 * 60 + 0, // Sleep time - 17 * 60 + 31); // Clock + state.model = mirage_ac_remote_model_t::KKG29AC1; + irac.mirage(&ac, state); ASSERT_EQ(expected_KKG29AC1, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index c63b7b1fd..cf73f3135 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -226,7 +226,7 @@ TEST(TestMirageAcClass, HumanReadable) { ac.setModel(mirage_ac_remote_model_t::KKG29AC1); EXPECT_EQ( "Model: 2 (KKG29AC1), Power: On, Mode: 2 (Cool), Temp: 25C, " - "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Light: -, " + "Fan: 0 (Auto), Turbo: Off, Sleep: Off, Quiet: Off, Light: -, " "Swing(V): Off, Swing(H): Off, " "Filter: Off, Clean: -, On Timer: Off, Off Timer: Off, " "IFeel: Off", From 812fa8f3cb0a665f95da51549bda661f3d5ea430 Mon Sep 17 00:00:00 2001 From: crankyoldgit Date: Sat, 6 Nov 2021 20:38:40 +1000 Subject: [PATCH 7/7] Additional unit test for `getModel()` Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1573#issuecomment-962362540 --- test/ir_Mirage_test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index cf73f3135..ad08918e2 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -581,4 +581,11 @@ TEST(TestMirageAcClass, getModel) { 0x56, 0x74, 0x00, 0x00, 0x12, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D}; EXPECT_EQ(mirage_ac_remote_model_t::KKG29AC1, IRMirageAc::getModel(KKG29AC1)); + + // https://github.com/crankyoldgit/IRremoteESP8266/issues/1573#issuecomment-962362540 + const uint8_t KKG29AC1_2[kMirageStateLength] = { + 0x56, 0x72, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19}; + EXPECT_EQ(mirage_ac_remote_model_t::KKG29AC1, + IRMirageAc::getModel(KKG29AC1_2)); }