[MIRAGE] Experimental detailed support for KKG29A-C1 remote. (#1660)

* Add support for KKG29A-C1 model.
  - IFeel
  - Sensor Temp
  - Quiet
  - Filter (UVC)
  - Clean
  - On & Off Timers
  - SwingH
* Add model detection.
* Add support to `IRac` class.
* Add & Update existing unit tests.
* Update supported models.

For #1573
This commit is contained in:
David Conran
2021-11-10 15:03:55 +10:00
committed by GitHub
parent 44512c9be0
commit 4c96689b18
11 changed files with 963 additions and 167 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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_

View File

@@ -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

View File

@@ -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";
stdAc::state_t state, r, p;
const char expected_KKG9AC1[] =
"Model: 1 (KKG9AC1), Power: On, Mode: 3 (Dry), Temp: 27C, "
"Fan: 2 (Medium), Turbo: Off, Sleep: On, Light: Off, "
"Swing(V): 9 (High), Clock: 17:31";
ac.begin();
irac.mirage(&ac,
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
ASSERT_EQ(expected, ac.toString());
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, IRAcUtils::resultAcToString(&ac._irsend.capture));
stdAc::state_t r, p;
ASSERT_EQ(expected_KKG9AC1, IRAcUtils::resultAcToString(&ac._irsend.capture));
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));
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_KKG29AC1,
IRAcUtils::resultAcToString(&ac._irsend.capture));
ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p));
}

View File

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