diff --git a/src/IRac.cpp b/src/IRac.cpp index 19bbcbb8b..e9331ae4b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1488,39 +1488,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] on The power setting. -/// @param[in] mode The operation mode setting. -/// @param[in] degrees The temperature setting in degrees. -/// @param[in] fan The speed setting for the fan. -/// @param[in] swingv The vertical swing setting. -/// @param[in] turbo Run the device in turbo mode. -/// @param[in] light Turn on the Light/Display. -/// @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, - 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->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->setTurbo(turbo); - // No Quiet setting available. - ac->setLight(light); - // No Filter setting available. - // No Clean setting available. - // 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 @@ -2536,6 +2507,11 @@ 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: + 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: // CKP models use a power mode toggle. if (desired.model == panasonic_ac_remote_model_t::kPanasonicCkp) @@ -2910,9 +2886,7 @@ 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, - send.sleep, send.clock); + mirage(&ac, send); break; } #endif // SEND_MIRAGE diff --git a/src/IRac.h b/src/IRac.h index a6ef06c90..3641f3574 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -332,11 +332,7 @@ void electra(IRElectraAc *ac, const bool light, const int16_t sleep = -1); #endif // SEND_MIDEA #if SEND_MIRAGE - void 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, - 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/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..a806937b1 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; @@ -38,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. @@ -105,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. @@ -115,6 +121,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); + // 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 @@ -129,6 +144,59 @@ 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. +/// @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 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; + // 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. +/// @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(const mirage_ac_remote_model_t model) { + if (model != _model) { // Only change things if we need to. + // Save the old settings. + 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. + state.model = model; + // Restore/Convert the settings. + fromCommon(state); + setOnTimer(ontimer); + setOffTimer(offtimer); + setIFeel(ifeel); + setSensorTemp(sensor); + } } /// Calculate and set the checksum values for the internal state. @@ -157,30 +225,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 ? kMirageAcKKG29AC1PowerOn : kMirageAcKKG29AC1PowerOff; + 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 == kMirageAcKKG29AC1PowerOn; + 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 +306,120 @@ 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; } +/// @note Light is a toggle on the KKG29AC1 model. +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; } +/// @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; + 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 +429,199 @@ 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 ? kMirageAcSwingVAuto : kMirageAcSwingVOff; + 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) { + // 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; + } +} + +/// 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. @@ -310,13 +638,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; + } } } @@ -334,12 +675,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; @@ -356,6 +710,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 +725,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,55 +735,113 @@ 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.fanspeed = toCommonFanSpeed(getFan(), _model); 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; } +/// 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 { 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); result += addTempToString(getTemp()); - result += addFanToString(_.Fan, kMirageAcFanHigh, - 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); + uint8_t fanlow; + uint8_t fanmed; + switch (_model) { + case mirage_ac_remote_model_t::KKG29AC1: + fanlow = kMirageAcKKG29AC1FanLow; + fanmed = kMirageAcKKG29AC1FanMed; + break; + default: // e.g. Model KKG9AC1 + fanlow = kMirageAcFanLow; + fanmed = kMirageAcFanMed; + } + result += addFanToString(_.Fan, kMirageAcFanHigh, fanlow, kMirageAcFanAuto, + kMirageAcFanAuto, fanmed); + result += addBoolToString(getTurbo(), kTurboStr); + 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); + 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: // e.g. Model KKG9AC1 + result += addBoolToString(getLight(), kLightStr); + 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..b2b39e759 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: Reykir 9000 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 }; }; @@ -81,12 +169,19 @@ 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 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,16 +229,37 @@ 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(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); 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; + void fromCommon(const stdAc::state_t state); String toString(void) const; #ifndef UNIT_TEST @@ -155,6 +271,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 8f783a048..f9f3c4d6d 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1354,30 +1354,55 @@ 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"; - - 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 + 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(); + + 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(); + 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: 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(); + 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)); 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..ad08918e2 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, Sleep: Off, Light: 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, Sleep: Off, Light: 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, Sleep: Off, Light: Off, " + "Swing(V): 0 (Off), Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -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) { @@ -182,10 +193,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, 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 // 0x56710000201A00000C000C26010041 @@ -194,9 +207,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, Sleep: Off, Light: Off, " + "Swing(V): 13 (Auto), Clock: 00:01", ac.toString()); const uint8_t SyntheticExample[kMirageStateLength] = { @@ -204,9 +217,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, Sleep: Off, Light: 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, Sleep: Off, Quiet: Off, Light: -, " + "Swing(V): Off, Swing(H): Off, " + "Filter: Off, Clean: -, On Timer: Off, Off Timer: Off, " + "IFeel: Off", ac.toString()); } @@ -266,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); @@ -278,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); @@ -290,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); @@ -302,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 @@ -310,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) { @@ -327,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()); @@ -341,4 +400,192 @@ 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) { + 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)); + + // 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)); }