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)); +}