diff --git a/examples/IRrecvDumpV2/IRrecvDumpV2.ino b/examples/IRrecvDumpV2/IRrecvDumpV2.ino index d72e0814c..5ffaecf05 100644 --- a/examples/IRrecvDumpV2/IRrecvDumpV2.ino +++ b/examples/IRrecvDumpV2/IRrecvDumpV2.ino @@ -38,6 +38,8 @@ #include #include #include +#include + // ==================== start of TUNEABLE PARAMETERS ==================== // An IR detector/demodulator is connected to GPIO pin 14 @@ -206,6 +208,13 @@ void dumpACInfo(decode_results *results) { description = ac.toString(); } #endif // DECODE_HITACHI_AC +#if DECODE_WHIRLPOOL_AC + if (results->decode_type == WHIRLPOOL_AC) { + IRWhirlpoolAc ac(0); + ac.setRaw(results->state); + description = ac.toString(); + } +#endif // DECODE_WHIRLPOOL_AC // If we got a human-readable description of the message, display it. if (description != "") Serial.println("Mesg Desc.: " + description); } diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 7864625a5..1cf73268b 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -459,6 +459,13 @@ uint8_t sumBytes(uint8_t *start, const uint16_t length, const uint8_t init) { return checksum; } +uint8_t xorBytes(uint8_t *start, const uint16_t length, const uint8_t init) { + uint8_t checksum = init; + uint8_t *ptr; + for (ptr = start; ptr - start < length; ptr++) checksum ^= *ptr; + return checksum; +} + uint64_t invertBits(const uint64_t data, const uint16_t nbits) { // No change if we are asked to invert no bits. if (nbits == 0) return data; diff --git a/src/IRutils.h b/src/IRutils.h index c17375d98..f4e890948 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -36,6 +36,7 @@ std::string resultToHexidecimal(const decode_results *result); bool hasACState(const decode_type_t protocol); uint16_t getCorrectedRawLength(const decode_results *results); uint8_t sumBytes(uint8_t *start, const uint16_t length, const uint8_t init = 0); +uint8_t xorBytes(uint8_t *start, const uint16_t length, const uint8_t init = 0); uint64_t invertBits(const uint64_t data, const uint16_t nbits); #endif // IRUTILS_H_ diff --git a/src/ir_Whirlpool.cpp b/src/ir_Whirlpool.cpp index 671513991..92b346192 100644 --- a/src/ir_Whirlpool.cpp +++ b/src/ir_Whirlpool.cpp @@ -3,8 +3,18 @@ // Code to emulate Whirlpool protocol compatible devices. // Should be compatible with: // * SPIS409L, SPIS412L, SPIW409L, SPIW412L, SPIW418L +// Remotes: +// * DG11J1-3A / DG11J1-04 +// * DG11J1-91 +// +// Note: Smart, iFeel, AroundU, PowerSave, & Silent modes are unsupported. +// Advanced 6thSense, Dehumidify, & Sleep modes are not supported. +// FYI: +// Dim == !Light +// Jet == Super == Turbo // +#include "ir_Whirlpool.h" #include #ifndef ARDUINO #include @@ -71,6 +81,479 @@ void IRsend::sendWhirlpoolAC(unsigned char data[], uint16_t nbytes, } #endif // SEND_WHIRLPOOL_AC +// Class for emulating a Whirlpool A/C remote. +// Decoding help from: +// @redmusicxd, @josh929800, @raducostea + +IRWhirlpoolAc::IRWhirlpoolAc(uint16_t pin) : _irsend(pin) { stateReset(); } + +void IRWhirlpoolAc::stateReset() { + for (uint8_t i = 2; i < kWhirlpoolAcStateLength; i++) remote_state[i] = 0x0; + remote_state[0] = 0x83; + remote_state[1] = 0x06; + remote_state[6] = 0x80; + _setTemp(kWhirlpoolAcAutoTemp); // Default to a sane value. +} + +void IRWhirlpoolAc::begin() { _irsend.begin(); } + +bool IRWhirlpoolAc::validChecksum(uint8_t state[], const uint16_t length) { + if (length > kWhirlpoolAcChecksumByte1 && + state[kWhirlpoolAcChecksumByte1] != + xorBytes(state + 2, kWhirlpoolAcChecksumByte1 - 1 - 2)) { + DPRINTLN("DEBUG: First Whirlpool AC checksum failed."); + return false; + } + if (length > kWhirlpoolAcChecksumByte2 && + state[kWhirlpoolAcChecksumByte2] != + xorBytes(state + kWhirlpoolAcChecksumByte1 + 1, + kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1)) { + DPRINTLN("DEBUG: Second Whirlpool AC checksum failed."); + return false; + } + // State is too short to have a checksum or everything checked out. + return true; +} + +// Update the checksum for the internal state. +void IRWhirlpoolAc::checksum(uint16_t length) { + if (length >= kWhirlpoolAcChecksumByte1) + remote_state[kWhirlpoolAcChecksumByte1] = + xorBytes(remote_state + 2, kWhirlpoolAcChecksumByte1 - 1 - 2); + if (length >= kWhirlpoolAcChecksumByte2) + remote_state[kWhirlpoolAcChecksumByte2] = + xorBytes(remote_state + kWhirlpoolAcChecksumByte1 + 1, + kWhirlpoolAcChecksumByte2 - kWhirlpoolAcChecksumByte1 - 1); +} + +#if SEND_WHIRLPOOL_AC +void IRWhirlpoolAc::send(const bool calcchecksum) { + if (calcchecksum) checksum(); + _irsend.sendWhirlpoolAC(remote_state); +} +#endif // SEND_WHIRLPOOL_AC + +uint8_t *IRWhirlpoolAc::getRaw(const bool calcchecksum) { + if (calcchecksum) checksum(); + return remote_state; +} + +void IRWhirlpoolAc::setRaw(const uint8_t new_code[], const uint16_t length) { + for (uint8_t i = 0; i < length && i < kWhirlpoolAcStateLength; i++) + remote_state[i] = new_code[i]; +} + +whirlpool_ac_remote_model_t IRWhirlpoolAc::getModel() { + if (remote_state[kWhirlpoolAcAltTempPos] & kWhirlpoolAcAltTempMask) + return DG11J191; + else + return DG11J13A; +} + +void IRWhirlpoolAc::setModel(const whirlpool_ac_remote_model_t model) { + switch (model) { + case DG11J191: + remote_state[kWhirlpoolAcAltTempPos] |= kWhirlpoolAcAltTempMask; + break; + case DG11J13A: + // FALL THRU + default: + remote_state[kWhirlpoolAcAltTempPos] &= ~kWhirlpoolAcAltTempMask; + } + _setTemp(_desiredtemp); // Different models have different temp values. +} + +// Return the temp. offset in deg C for the current model. +int8_t IRWhirlpoolAc::getTempOffset() { + switch (getModel()) { + case DG11J191: + return -2; + break; + default: + return 0; + } +} + +// Set the temp. in deg C +void IRWhirlpoolAc::_setTemp(const uint8_t temp, const bool remember) { + if (remember) _desiredtemp = temp; + int8_t offset = getTempOffset(); // Cache the min temp for the model. + uint8_t newtemp = std::max((uint8_t)(kWhirlpoolAcMinTemp + offset), temp); + newtemp = std::min((uint8_t)(kWhirlpoolAcMaxTemp + offset), newtemp); + remote_state[kWhirlpoolAcTempPos] = + (remote_state[kWhirlpoolAcTempPos] & ~kWhirlpoolAcTempMask) | + ((newtemp - (kWhirlpoolAcMinTemp + offset)) << 4); +} + +// Set the temp. in deg C +void IRWhirlpoolAc::setTemp(const uint8_t temp) { + _setTemp(temp); + setSuper(false); // Changing temp cancels Super/Jet mode. + setCommand(kWhirlpoolAcCommandTemp); +} + +// Return the set temp. in deg C +uint8_t IRWhirlpoolAc::getTemp() { + return ((remote_state[kWhirlpoolAcTempPos] & kWhirlpoolAcTempMask) >> 4) + + + kWhirlpoolAcMinTemp + getTempOffset(); +} + +void IRWhirlpoolAc::_setMode(const uint8_t mode) { + switch (mode) { + case kWhirlpoolAcAuto: + setFan(kWhirlpoolAcFanAuto); + _setTemp(kWhirlpoolAcAutoTemp, false); + setSleep(false); // Cancel sleep mode when in auto/6thsense mode. + // FALL THRU + case kWhirlpoolAcHeat: + case kWhirlpoolAcCool: + case kWhirlpoolAcDry: + case kWhirlpoolAcFan: + remote_state[kWhirlpoolAcModePos] &= ~kWhirlpoolAcModeMask; + remote_state[kWhirlpoolAcModePos] |= mode; + setCommand(kWhirlpoolAcCommandMode); + break; + default: + return; + } + if (mode == kWhirlpoolAcAuto) setCommand(kWhirlpoolAcCommand6thSense); +} + +void IRWhirlpoolAc::setMode(const uint8_t mode) { + setSuper(false); // Changing mode cancels Super/Jet mode. + _setMode(mode); +} + +uint8_t IRWhirlpoolAc::getMode() { + return remote_state[kWhirlpoolAcModePos] & kWhirlpoolAcModeMask; +} + +void IRWhirlpoolAc::setFan(const uint8_t speed) { + switch (speed) { + case kWhirlpoolAcFanAuto: + case kWhirlpoolAcFanLow: + case kWhirlpoolAcFanMedium: + case kWhirlpoolAcFanHigh: + remote_state[kWhirlpoolAcFanPos] = + (remote_state[kWhirlpoolAcFanPos] & ~kWhirlpoolAcFanMask) | speed; + setSuper(false); // Changing fan speed cancels Super/Jet mode. + setCommand(kWhirlpoolAcCommandFanSpeed); + break; + } +} + +uint8_t IRWhirlpoolAc::getFan() { + return remote_state[kWhirlpoolAcFanPos] & kWhirlpoolAcFanMask; +} + +void IRWhirlpoolAc::setSwing(const bool on) { + if (on) { + remote_state[kWhirlpoolAcFanPos] |= kWhirlpoolAcSwing1Mask; + remote_state[kWhirlpoolAcOffTimerPos] |= kWhirlpoolAcSwing2Mask; + } else { + remote_state[kWhirlpoolAcFanPos] &= ~kWhirlpoolAcSwing1Mask; + remote_state[kWhirlpoolAcOffTimerPos] &= ~kWhirlpoolAcSwing2Mask; + } + setCommand(kWhirlpoolAcCommandSwing); +} + +bool IRWhirlpoolAc::getSwing() { + return (remote_state[kWhirlpoolAcFanPos] & kWhirlpoolAcSwing1Mask) && + (remote_state[kWhirlpoolAcOffTimerPos] & kWhirlpoolAcSwing2Mask); +} + +void IRWhirlpoolAc::setLight(const bool on) { + if (on) + remote_state[kWhirlpoolAcClockPos] &= ~kWhirlpoolAcLightMask; + else + remote_state[kWhirlpoolAcClockPos] |= kWhirlpoolAcLightMask; +} + +bool IRWhirlpoolAc::getLight() { + return !(remote_state[kWhirlpoolAcClockPos] & kWhirlpoolAcLightMask); +} + +void IRWhirlpoolAc::setTime(const uint16_t pos, + const uint16_t minspastmidnight) { + // Hours + remote_state[pos] &= ~kWhirlpoolAcHourMask; + remote_state[pos] |= (minspastmidnight / 60) % 24; + // Minutes + remote_state[pos + 1] &= ~kWhirlpoolAcMinuteMask; + remote_state[pos + 1] |= minspastmidnight % 60; +} + +uint16_t IRWhirlpoolAc::getTime(const uint16_t pos) { + return (remote_state[pos] & kWhirlpoolAcHourMask) * 60 + + (remote_state[pos + 1] & kWhirlpoolAcMinuteMask); +} + +bool IRWhirlpoolAc::isTimerEnabled(const uint16_t pos) { + return remote_state[pos - 1] & kWhirlpoolAcTimerEnableMask; +} + +void IRWhirlpoolAc::enableTimer(const uint16_t pos, const bool state) { + if (state) + remote_state[pos - 1] |= kWhirlpoolAcTimerEnableMask; + else + remote_state[pos - 1] &= ~kWhirlpoolAcTimerEnableMask; +} + +void IRWhirlpoolAc::setClock(const uint16_t minspastmidnight) { + setTime(kWhirlpoolAcClockPos, minspastmidnight); +} + +uint16_t IRWhirlpoolAc::getClock() { return getTime(kWhirlpoolAcClockPos); } + +void IRWhirlpoolAc::setOffTimer(const uint16_t minspastmidnight) { + setTime(kWhirlpoolAcOffTimerPos, minspastmidnight); +} + +uint16_t IRWhirlpoolAc::getOffTimer() { + return getTime(kWhirlpoolAcOffTimerPos); +} + +bool IRWhirlpoolAc::isOffTimerEnabled() { + return isTimerEnabled(kWhirlpoolAcOffTimerPos); +} + +void IRWhirlpoolAc::enableOffTimer(const bool state) { + enableTimer(kWhirlpoolAcOffTimerPos, state); + setCommand(kWhirlpoolAcCommandOffTimer); +} + +void IRWhirlpoolAc::setOnTimer(const uint16_t minspastmidnight) { + setTime(kWhirlpoolAcOnTimerPos, minspastmidnight); +} + +uint16_t IRWhirlpoolAc::getOnTimer() { return getTime(kWhirlpoolAcOnTimerPos); } + +bool IRWhirlpoolAc::isOnTimerEnabled() { + return isTimerEnabled(kWhirlpoolAcOnTimerPos); +} + +void IRWhirlpoolAc::enableOnTimer(const bool state) { + enableTimer(kWhirlpoolAcOnTimerPos, state); + setCommand(kWhirlpoolAcCommandOnTimer); +} + +void IRWhirlpoolAc::setPowerToggle(const bool on) { + if (on) + remote_state[kWhirlpoolAcPowerTogglePos] |= kWhirlpoolAcPowerToggleMask; + else + remote_state[kWhirlpoolAcPowerTogglePos] &= ~kWhirlpoolAcPowerToggleMask; + setSuper(false); // Changing power cancels Super/Jet mode. + setCommand(kWhirlpoolAcCommandPower); +} + +bool IRWhirlpoolAc::getPowerToggle() { + return remote_state[kWhirlpoolAcPowerTogglePos] & kWhirlpoolAcPowerToggleMask; +} + +uint8_t IRWhirlpoolAc::getCommand() { + return remote_state[kWhirlpoolAcCommandPos]; +} + +void IRWhirlpoolAc::setSleep(const bool on) { + if (on) { + remote_state[kWhirlpoolAcSleepPos] |= kWhirlpoolAcSleepMask; + setFan(kWhirlpoolAcFanLow); + } else { + remote_state[kWhirlpoolAcSleepPos] &= ~kWhirlpoolAcSleepMask; + } + setCommand(kWhirlpoolAcCommandSleep); +} + +bool IRWhirlpoolAc::getSleep() { + return remote_state[kWhirlpoolAcSleepPos] & kWhirlpoolAcSleepMask; +} + +// AKA Jet/Turbo mode. +void IRWhirlpoolAc::setSuper(const bool on) { + if (on) { + setFan(kWhirlpoolAcFanHigh); + switch (getMode()) { + case kWhirlpoolAcHeat: + setTemp(kWhirlpoolAcMaxTemp + getTempOffset()); + break; + case kWhirlpoolAcCool: + default: + setTemp(kWhirlpoolAcMinTemp + getTempOffset()); + setMode(kWhirlpoolAcCool); + break; + } + remote_state[kWhirlpoolAcSuperPos] |= kWhirlpoolAcSuperMask; + } else { + remote_state[kWhirlpoolAcSuperPos] &= ~kWhirlpoolAcSuperMask; + } + setCommand(kWhirlpoolAcCommandSuper); +} + +bool IRWhirlpoolAc::getSuper() { + return remote_state[kWhirlpoolAcSuperPos] & kWhirlpoolAcSuperMask; +} + +void IRWhirlpoolAc::setCommand(const uint8_t code) { + remote_state[kWhirlpoolAcCommandPos] = code; +} + +#ifdef ARDUINO +String IRWhirlpoolAc::timeToString(const uint16_t minspastmidnight) { + String result = ""; +#else +std::string IRWhirlpoolAc::timeToString(const uint16_t minspastmidnight) { + std::string result = ""; +#endif // ARDUINO + uint8_t hours = minspastmidnight / 60; + if (hours < 10) result += "0"; + result += uint64ToString(hours); + result += ":"; + uint8_t mins = minspastmidnight % 60; + if (mins < 10) result += "0"; + result += uint64ToString(mins); + return result; +} + +// Convert the internal state into a human readable string. +#ifdef ARDUINO +String IRWhirlpoolAc::toString() { + String result = ""; +#else +std::string IRWhirlpoolAc::toString() { + std::string result = ""; +#endif // ARDUINO + result += "Model: " + uint64ToString(getModel()); + switch (getModel()) { + case DG11J191: + result += " (DG11J191)"; + break; + case DG11J13A: + result += " (DG11J13A)"; + break; + default: + result += " (UNKNOWN)"; + } + result += ", Power toggle: "; + if (getPowerToggle()) + result += "On"; + else + result += "Off"; + result += ", Mode: " + uint64ToString(getMode()); + switch (getMode()) { + case kWhirlpoolAcHeat: + result += " (HEAT)"; + break; + case kWhirlpoolAcAuto: + result += " (AUTO)"; + break; + case kWhirlpoolAcCool: + result += " (COOL)"; + break; + case kWhirlpoolAcDry: + result += " (DRY)"; + break; + case kWhirlpoolAcFan: + result += " (FAN)"; + break; + default: + result += " (UNKNOWN)"; + } + result += ", Temp: " + uint64ToString(getTemp()) + "C"; + result += ", Fan: " + uint64ToString(getFan()); + switch (getFan()) { + case kWhirlpoolAcFanAuto: + result += " (AUTO)"; + break; + case kWhirlpoolAcFanHigh: + result += " (HIGH)"; + break; + case kWhirlpoolAcFanMedium: + result += " (MEDIUM)"; + break; + case kWhirlpoolAcFanLow: + result += " (LOW)"; + break; + default: + result += " (UNKNOWN)"; + break; + } + result += ", Swing: "; + if (getSwing()) + result += "On"; + else + result += "Off"; + result += ", Light: "; + if (getLight()) + result += "On"; + else + result += "Off"; + result += ", Clock: "; + result += timeToString(getClock()); + result += ", On Timer: "; + if (isOnTimerEnabled()) + result += timeToString(getOnTimer()); + else + result += "Off"; + result += ", Off Timer: "; + if (isOffTimerEnabled()) + result += timeToString(getOffTimer()); + else + result += "Off"; + result += ", Sleep: "; + if (getSleep()) + result += "On"; + else + result += "Off"; + result += ", Super: "; + if (getSuper()) + result += "On"; + else + result += "Off"; + result += ", Command: " + uint64ToString(getCommand()); + switch (getCommand()) { + case kWhirlpoolAcCommandLight: + result += " (LIGHT)"; + break; + case kWhirlpoolAcCommandPower: + result += " (POWER)"; + break; + case kWhirlpoolAcCommandTemp: + result += " (TEMP)"; + break; + case kWhirlpoolAcCommandSleep: + result += " (SLEEP)"; + break; + case kWhirlpoolAcCommandSuper: + result += " (SUPER)"; + break; + case kWhirlpoolAcCommandOnTimer: + result += " (ONTIMER)"; + break; + case kWhirlpoolAcCommandMode: + result += " (MODE)"; + break; + case kWhirlpoolAcCommandSwing: + result += " (SWING)"; + break; + case kWhirlpoolAcCommandIFeel: + result += " (IFEEL)"; + break; + case kWhirlpoolAcCommandFanSpeed: + result += " (FANSPEED)"; + break; + case kWhirlpoolAcCommand6thSense: + result += " (6THSENSE)"; + break; + case kWhirlpoolAcCommandOffTimer: + result += " (OFFTIMER)"; + break; + default: + result += " (UNKNOWN)"; + break; + } + return result; +} + #if DECODE_WHIRLPOOL_AC // Decode the supplied Whirlpool A/C message. // @@ -81,7 +564,7 @@ void IRsend::sendWhirlpoolAC(unsigned char data[], uint16_t nbytes, // Returns: // boolean: True if it can decode it, false if it can't. // -// Status: ALPHA / Untested. +// Status: STABLE / Working as intended. // // // Ref: @@ -136,6 +619,8 @@ bool IRrecv::decodeWhirlpoolAC(decode_results *results, uint16_t nbits, if (strict) { // Re-check we got the correct size/length due to the way we read the data. if (dataBitsSoFar != kWhirlpoolAcBits) return false; + if (!IRWhirlpoolAc::validChecksum(results->state, dataBitsSoFar / 8)) + return false; } // Success diff --git a/src/ir_Whirlpool.h b/src/ir_Whirlpool.h new file mode 100644 index 000000000..f71589c49 --- /dev/null +++ b/src/ir_Whirlpool.h @@ -0,0 +1,161 @@ +// Whirlpool A/C +// +// Copyright 2018 David Conran + +#ifndef IR_WHIRLPOOL_H_ +#define IR_WHIRLPOOL_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include +#else +#include +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" + +// WW WW HH HH IIIII RRRRRR LL PPPPPP OOOOO OOOOO LL +// WW WW HH HH III RR RR LL PP PP OO OO OO OO LL +// WW W WW HHHHHHH III RRRRRR LL PPPPPP OO OO OO OO LL +// WW WWW WW HH HH III RR RR LL PP OO OO OO OO LL +// WW WW HH HH IIIII RR RR LLLLLLL PP OOOO0 OOOO0 LLLLLLL + +// Ref: +// https://github.com/markszabo/IRremoteESP8266/issues/509 + +// Constants +const uint8_t kWhirlpoolAcChecksumByte1 = 13; +const uint8_t kWhirlpoolAcChecksumByte2 = kWhirlpoolAcStateLength - 1; +const uint8_t kWhirlpoolAcHeat = 0; +const uint8_t kWhirlpoolAcAuto = 1; +const uint8_t kWhirlpoolAcCool = 2; +const uint8_t kWhirlpoolAcDry = 3; +const uint8_t kWhirlpoolAcFan = 4; +const uint8_t kWhirlpoolAcModeMask = 0b00000111; +const uint8_t kWhirlpoolAcModePos = 3; +const uint8_t kWhirlpoolAcFanAuto = 0; +const uint8_t kWhirlpoolAcFanHigh = 1; +const uint8_t kWhirlpoolAcFanMedium = 2; +const uint8_t kWhirlpoolAcFanLow = 3; +const uint8_t kWhirlpoolAcFanMask = 0b00000011; +const uint8_t kWhirlpoolAcFanPos = 2; +const uint8_t kWhirlpoolAcMinTemp = 18; // 18C (DG11J1-3A), 16C (DG11J1-91) +const uint8_t kWhirlpoolAcMaxTemp = 32; // 32C (DG11J1-3A), 30C (DG11J1-91) +const uint8_t kWhirlpoolAcAutoTemp = 23; // 23C +const uint8_t kWhirlpoolAcTempMask = 0b11110000; +const uint8_t kWhirlpoolAcTempPos = 3; +const uint8_t kWhirlpoolAcSwing1Mask = 0b10000000; +const uint8_t kWhirlpoolAcSwing2Mask = 0b01000000; +const uint8_t kWhirlpoolAcLightMask = 0b00100000; +const uint8_t kWhirlpoolAcPowerToggleMask = 0b00000100; +const uint8_t kWhirlpoolAcPowerTogglePos = 2; +const uint8_t kWhirlpoolAcSleepMask = 0b00001000; +const uint8_t kWhirlpoolAcSleepPos = 2; +const uint8_t kWhirlpoolAcSuperMask = 0b10010000; +const uint8_t kWhirlpoolAcSuperPos = 5; +const uint8_t kWhirlpoolAcHourMask = 0b00011111; +const uint8_t kWhirlpoolAcMinuteMask = 0b00111111; +const uint8_t kWhirlpoolAcTimerEnableMask = 0b10000000; +const uint8_t kWhirlpoolAcClockPos = 6; +const uint8_t kWhirlpoolAcOffTimerPos = 8; +const uint8_t kWhirlpoolAcOnTimerPos = 10; +const uint8_t kWhirlpoolAcCommandPos = 15; +const uint8_t kWhirlpoolAcCommandLight = 0x00; +const uint8_t kWhirlpoolAcCommandPower = 0x01; +const uint8_t kWhirlpoolAcCommandTemp = 0x02; +const uint8_t kWhirlpoolAcCommandSleep = 0x03; +const uint8_t kWhirlpoolAcCommandSuper = 0x04; +const uint8_t kWhirlpoolAcCommandOnTimer = 0x05; +const uint8_t kWhirlpoolAcCommandMode = 0x06; +const uint8_t kWhirlpoolAcCommandSwing = 0x07; +const uint8_t kWhirlpoolAcCommandIFeel = 0x0D; +const uint8_t kWhirlpoolAcCommandFanSpeed = 0x11; +const uint8_t kWhirlpoolAcCommand6thSense = 0x17; +const uint8_t kWhirlpoolAcCommandOffTimer = 0x1D; +const uint8_t kWhirlpoolAcAltTempMask = 0b00001000; +const uint8_t kWhirlpoolAcAltTempPos = 18; + +enum whirlpool_ac_remote_model_t { + // TODO(crankyoldgit): Replace with correct model numbers when we have them. + DG11J13A = 1, // DG11J1-04 too + DG11J191, +}; + +// Classes +class IRWhirlpoolAc { + public: + explicit IRWhirlpoolAc(uint16_t pin); + + void stateReset(); +#if SEND_WHIRLPOOL_AC + void send(const bool calcchecksum = true); +#endif // SEND_WHIRLPOOL_AC + void begin(); + void on(); + void off(); + void setPowerToggle(const bool on); + bool getPowerToggle(); + void setSleep(const bool on); + bool getSleep(); + void setSuper(const bool on); + bool getSuper(); + void setTemp(const uint8_t temp); + uint8_t getTemp(); + void setFan(const uint8_t speed); + uint8_t getFan(); + void setMode(const uint8_t mode); + uint8_t getMode(); + void setSwing(const bool on); + bool getSwing(); + void setLight(const bool on); + bool getLight(); + uint16_t getClock(); + void setClock(const uint16_t minspastmidnight); + uint16_t getOnTimer(); + void setOnTimer(const uint16_t minspastmidnight); + void enableOnTimer(const bool state); + bool isOnTimerEnabled(); + uint16_t getOffTimer(); + void setOffTimer(const uint16_t minspastmidnight); + void enableOffTimer(const bool state); + bool isOffTimerEnabled(); + void setCommand(const uint8_t code); + uint8_t getCommand(); + whirlpool_ac_remote_model_t getModel(); + void setModel(const whirlpool_ac_remote_model_t model); + uint8_t* getRaw(const bool calcchecksum = true); + void setRaw(const uint8_t new_code[], + const uint16_t length = kWhirlpoolAcStateLength); + static bool validChecksum(uint8_t state[], + const uint16_t length = kWhirlpoolAcStateLength); +#ifdef ARDUINO + String toString(); +#else + std::string toString(); +#endif + +#ifndef UNIT_TEST + + private: +#endif + // The state of the IR remote in IR code form. + uint8_t remote_state[kWhirlpoolAcStateLength]; + IRsend _irsend; + uint8_t _desiredtemp; + void checksum(const uint16_t length = kWhirlpoolAcStateLength); + uint16_t getTime(const uint16_t pos); + void setTime(const uint16_t pos, const uint16_t minspastmidnight); + bool isTimerEnabled(const uint16_t pos); + void enableTimer(const uint16_t pos, const bool state); + void _setTemp(const uint8_t temp, const bool remember = true); + void _setMode(const uint8_t mode); + int8_t getTempOffset(); +#ifdef ARDUINO + String timeToString(uint16_t minspastmidnight); +#else + std::string timeToString(uint16_t minspastmidnight); +#endif +}; + +#endif // IR_WHIRLPOOL_H_ diff --git a/test/Makefile b/test/Makefile index d53014183..4c0cb3650 100644 --- a/test/Makefile +++ b/test/Makefile @@ -98,7 +98,8 @@ PROTOCOLS_H = $(USER_DIR)/ir_Argo.h \ $(USER_DIR)/ir_Trotec.h \ $(USER_DIR)/ir_Fujitsu.h \ $(USER_DIR)/ir_LG.h \ - $(USER_DIR)/ir_Panasonic.h + $(USER_DIR)/ir_Panasonic.h \ + $(USER_DIR)/ir_Whirlpool.h # Common object files COMMON_OBJ = IRutils.o IRtimer.o IRsend.o IRrecv.o ir_GlobalCache.o \ $(PROTOCOLS) gtest_main.a diff --git a/test/ir_Whirlpool_test.cpp b/test/ir_Whirlpool_test.cpp index c30cb21d3..f0c4ebece 100644 --- a/test/ir_Whirlpool_test.cpp +++ b/test/ir_Whirlpool_test.cpp @@ -1,5 +1,6 @@ // Copyright 2018 David Conran +#include "ir_Whirlpool.h" #include "IRrecv.h" #include "IRrecv_test.h" #include "IRsend.h" @@ -64,6 +65,92 @@ TEST(TestDecodeWhirlpoolAC, SyntheticDecode) { EXPECT_EQ(WHIRLPOOL_AC, irsend.capture.decode_type); EXPECT_EQ(kWhirlpoolAcBits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + IRWhirlpoolAc ac(0); + ac.setRaw(irsend.capture.state); + EXPECT_EQ( + "Model: 1 (DG11J13A), Power toggle: Off, Mode: 1 (AUTO), Temp: 25C, " + "Fan: 0 (AUTO), Swing: Off, Light: On, Clock: 17:31, On Timer: Off, " + "Off Timer: Off, Sleep: Off, Super: Off, Command: 2 (TEMP)", + ac.toString()); +} + +TEST(TestDecodeWhirlpoolAC, Real26CFanAutoCoolingSwingOnClock1918) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + uint8_t expectedState[kWhirlpoolAcStateLength] = { + 0x83, 0x06, 0x80, 0x82, 0x00, 0x00, 0x93, 0x12, 0x40, 0x00, 0x00, + 0x00, 0x00, 0xC3, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x07}; + irsend.sendWhirlpoolAC(expectedState); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(WHIRLPOOL_AC, irsend.capture.decode_type); + EXPECT_EQ(kWhirlpoolAcBits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + IRWhirlpoolAc ac(0); + ac.setRaw(irsend.capture.state); + EXPECT_EQ( + "Model: 1 (DG11J13A), Power toggle: Off, Mode: 2 (COOL), Temp: 26C, " + "Fan: 0 (AUTO), Swing: On, Light: On, Clock: 19:18, On Timer: Off, " + "Off Timer: Off, Sleep: Off, Super: Off, Command: 7 (SWING)", + ac.toString()); +} + +TEST(TestDecodeWhirlpoolAC, RealTimerExample) { + IRsendTest irsend(0); + IRrecv irrecv(0); + irsend.begin(); + + irsend.reset(); + // Dehumidify timer on 7:40 off 8:05 + uint16_t rawData[343] = { + 9092, 4556, 604, 1664, 604, 1674, 630, 514, 630, 518, 628, 522, + 604, 550, 628, 530, 602, 1680, 630, 508, 630, 1644, 604, 1674, + 604, 544, 604, 548, 630, 524, 604, 554, 620, 530, 630, 506, + 602, 538, 602, 542, 604, 542, 604, 546, 630, 524, 602, 556, + 628, 518, 604, 1666, 632, 1644, 604, 540, 602, 546, 604, 1680, + 604, 1684, 604, 1686, 630, 520, 602, 534, 606, 538, 602, 540, + 604, 544, 604, 548, 602, 552, 630, 528, 602, 546, 602, 536, + 628, 510, 606, 540, 604, 544, 630, 522, 604, 554, 600, 554, + 602, 528, 602, 8032, 604, 1666, 604, 1668, 602, 1676, 630, 518, + 630, 520, 602, 550, 604, 554, 604, 1678, 630, 1640, 602, 1672, + 602, 542, 602, 544, 628, 522, 630, 1658, 604, 554, 628, 1652, + 630, 508, 602, 538, 630, 514, 630, 1652, 602, 546, 604, 550, + 602, 554, 602, 546, 630, 1638, 604, 536, 630, 1646, 602, 544, + 628, 522, 632, 524, 628, 528, 602, 1686, 594, 1666, 604, 1670, + 602, 1674, 632, 516, 604, 546, 638, 518, 622, 534, 628, 518, + 604, 532, 604, 536, 600, 550, 622, 1652, 630, 520, 602, 1684, + 602, 554, 602, 544, 630, 506, 628, 512, 602, 540, 628, 518, + 602, 550, 602, 552, 604, 554, 602, 544, 628, 1642, 602, 536, + 632, 1646, 630, 516, 602, 1680, 630, 1656, 604, 1688, 602, 1660, + 602, 8030, 604, 532, 604, 536, 604, 540, 602, 544, 628, 522, + 602, 552, 602, 556, 602, 544, 602, 1666, 630, 510, 602, 1674, + 604, 544, 628, 522, 602, 552, 630, 526, 628, 520, 602, 534, + 630, 510, 604, 540, 602, 544, 606, 544, 604, 550, 604, 554, + 602, 544, 604, 534, 602, 538, 602, 542, 604, 542, 604, 546, + 604, 550, 632, 526, 604, 544, 630, 506, 604, 536, 604, 540, + 628, 518, 602, 548, 604, 550, 604, 552, 630, 516, 602, 534, + 604, 536, 630, 512, 604, 544, 602, 548, 630, 524, 602, 554, + 602, 542, 604, 1666, 606, 532, 630, 1644, 602, 544, 630, 520, + 604, 550, 604, 554, 602, 526, 598}; + uint8_t expectedState[kWhirlpoolAcStateLength] = { + 0x83, 0x06, 0x00, 0x73, 0x00, 0x00, 0x87, 0xA3, 0x08, 0x85, 0x07, + 0x28, 0x00, 0xF5, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x05}; + irsend.sendRaw(rawData, 343, 38000); + irsend.makeDecodeResult(); + EXPECT_TRUE(irrecv.decode(&irsend.capture)); + EXPECT_EQ(WHIRLPOOL_AC, irsend.capture.decode_type); + EXPECT_EQ(kWhirlpoolAcBits, irsend.capture.bits); + EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + IRWhirlpoolAc ac(0); + ac.setRaw(irsend.capture.state); + EXPECT_EQ( + "Model: 1 (DG11J13A), Power toggle: Off, Mode: 3 (DRY), Temp: 25C, " + "Fan: 0 (AUTO), Swing: Off, Light: On, Clock: 07:35, On Timer: 07:40, " + "Off Timer: 08:05, Sleep: Off, Super: Off, Command: 5 (ONTIMER)", + ac.toString()); } // Decode a recorded example @@ -115,4 +202,383 @@ TEST(TestDecodeWhirlpoolAC, RealExampleDecode) { EXPECT_EQ(WHIRLPOOL_AC, irsend.capture.decode_type); EXPECT_EQ(kWhirlpoolAcBits, irsend.capture.bits); EXPECT_STATE_EQ(expectedState, irsend.capture.state, irsend.capture.bits); + IRWhirlpoolAc ac(0); + ac.setRaw(irsend.capture.state); + EXPECT_EQ( + "Model: 1 (DG11J13A), Power toggle: Off, Mode: 1 (AUTO), Temp: 25C, " + "Fan: 0 (AUTO), Swing: Off, Light: On, Clock: 17:31, On Timer: Off, " + "Off Timer: Off, Sleep: Off, Super: Off, Command: 2 (TEMP)", + ac.toString()); +} + +// Tests for IRWhirlpoolAc class. + +TEST(TestIRWhirlpoolAcClass, SetAndGetRaw) { + uint8_t expectedState[kWhirlpoolAcStateLength] = { + 0x83, 0x06, 0x10, 0x71, 0x00, 0x00, 0x91, 0x1F, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xEF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02}; + IRWhirlpoolAc ac(0); + ac.setRaw(expectedState); + EXPECT_STATE_EQ(expectedState, ac.getRaw(), kWhirlpoolAcBits); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetTemp) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + ac.setModel(DG11J13A); + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); + EXPECT_EQ(kWhirlpoolAcCommandTemp, ac.getCommand()); + ac.setTemp(kWhirlpoolAcMinTemp); + EXPECT_EQ(kWhirlpoolAcMinTemp, ac.getTemp()); + ac.setTemp(kWhirlpoolAcMinTemp - 1); + EXPECT_EQ(kWhirlpoolAcMinTemp, ac.getTemp()); + ac.setTemp(kWhirlpoolAcMaxTemp); + EXPECT_EQ(kWhirlpoolAcMaxTemp, ac.getTemp()); + ac.setTemp(kWhirlpoolAcMaxTemp + 1); + EXPECT_EQ(kWhirlpoolAcMaxTemp, ac.getTemp()); + + ac.setModel(DG11J191); // Has a -2 offset on min/max temps. + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); + EXPECT_EQ(kWhirlpoolAcCommandTemp, ac.getCommand()); + ac.setTemp(kWhirlpoolAcMinTemp - 2); + EXPECT_EQ(kWhirlpoolAcMinTemp - 2, ac.getTemp()); + ac.setTemp(kWhirlpoolAcMinTemp - 2 - 1); + EXPECT_EQ(kWhirlpoolAcMinTemp - 2 , ac.getTemp()); + ac.setTemp(kWhirlpoolAcMaxTemp - 2); + EXPECT_EQ(kWhirlpoolAcMaxTemp - 2, ac.getTemp()); + ac.setTemp(kWhirlpoolAcMaxTemp - 2 + 1); + EXPECT_EQ(kWhirlpoolAcMaxTemp - 2, ac.getTemp()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetMode) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + ac.setMode(kWhirlpoolAcCool); + EXPECT_EQ(kWhirlpoolAcCool, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcCommandMode, ac.getCommand()); + ac.setMode(kWhirlpoolAcHeat); + EXPECT_EQ(kWhirlpoolAcHeat, ac.getMode()); + ac.setMode(kWhirlpoolAcAuto); + EXPECT_EQ(kWhirlpoolAcAuto, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcCommand6thSense, ac.getCommand()); + ac.setMode(kWhirlpoolAcDry); + EXPECT_EQ(kWhirlpoolAcDry, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcCommandMode, ac.getCommand()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetFan) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + ac.setFan(kWhirlpoolAcFanAuto); + EXPECT_EQ(kWhirlpoolAcFanAuto, ac.getFan()); + EXPECT_EQ(kWhirlpoolAcCommandFanSpeed, ac.getCommand()); + ac.setFan(kWhirlpoolAcFanLow); + EXPECT_EQ(kWhirlpoolAcFanLow, ac.getFan()); + ac.setFan(kWhirlpoolAcFanMedium); + EXPECT_EQ(kWhirlpoolAcFanMedium, ac.getFan()); + ac.setFan(kWhirlpoolAcFanHigh); + EXPECT_EQ(kWhirlpoolAcFanHigh, ac.getFan()); + ac.setFan(kWhirlpoolAcFanAuto); + EXPECT_EQ(kWhirlpoolAcFanAuto, ac.getFan()); + + // Known state with a non-auto fan mode. + const uint8_t state[21] = {0x83, 0x06, 0x0B, 0x82, 0x00, 0x00, 0x93, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, + 0x00, 0x03, 0x00, 0x00, 0x08, 0x00, 0x0B}; + ac.setRaw(state); + EXPECT_EQ(kWhirlpoolAcFanLow, ac.getFan()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetSwing) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + ac.setSwing(true); + EXPECT_TRUE(ac.getSwing()); + EXPECT_EQ(kWhirlpoolAcCommandSwing, ac.getCommand()); + ac.setSwing(false); + EXPECT_FALSE(ac.getSwing()); + ac.setSwing(true); + EXPECT_TRUE(ac.getSwing()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetLight) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_FALSE(ac.getLight()); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetClock) { + IRWhirlpoolAc ac(0); + ac.setClock(0); + EXPECT_EQ(0, ac.getClock()); + EXPECT_EQ("00:00", ac.timeToString(ac.getClock())); + ac.setClock(1); + EXPECT_EQ(1, ac.getClock()); + EXPECT_EQ("00:01", ac.timeToString(ac.getClock())); + ac.setClock(12 * 60 + 34); + EXPECT_EQ(12 * 60 + 34, ac.getClock()); + EXPECT_EQ("12:34", ac.timeToString(ac.getClock())); + ac.setClock(7 * 60 + 5); + EXPECT_EQ(7 * 60 + 5, ac.getClock()); + EXPECT_EQ("07:05", ac.timeToString(ac.getClock())); + ac.setClock(23 * 60 + 59); + EXPECT_EQ(23 * 60 + 59, ac.getClock()); + EXPECT_EQ("23:59", ac.timeToString(ac.getClock())); + ac.setClock(24 * 60 + 0); + EXPECT_EQ(0, ac.getClock()); + EXPECT_EQ("00:00", ac.timeToString(ac.getClock())); + ac.setClock(25 * 60 + 23); + EXPECT_EQ(1 * 60 + 23, ac.getClock()); + EXPECT_EQ("01:23", ac.timeToString(ac.getClock())); +} + +TEST(TestIRWhirlpoolAcClass, OnOffTimers) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); // Clear the previous command. + + // On Timer + ac.enableOnTimer(false); + ac.setOnTimer(0); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ("00:00", ac.timeToString(ac.getOnTimer())); + EXPECT_FALSE(ac.isOnTimerEnabled()); + EXPECT_EQ(kWhirlpoolAcCommandOnTimer, ac.getCommand()); + ac.setOnTimer(1); + EXPECT_EQ(1, ac.getOnTimer()); + EXPECT_EQ("00:01", ac.timeToString(ac.getOnTimer())); + ac.enableOnTimer(true); + ac.setOnTimer(12 * 60 + 34); + EXPECT_EQ(12 * 60 + 34, ac.getOnTimer()); + EXPECT_EQ("12:34", ac.timeToString(ac.getOnTimer())); + EXPECT_TRUE(ac.isOnTimerEnabled()); + ac.setOnTimer(7 * 60 + 5); + EXPECT_EQ(7 * 60 + 5, ac.getOnTimer()); + EXPECT_EQ("07:05", ac.timeToString(ac.getOnTimer())); + ac.setOnTimer(23 * 60 + 59); + EXPECT_EQ(23 * 60 + 59, ac.getOnTimer()); + EXPECT_EQ("23:59", ac.timeToString(ac.getOnTimer())); + ac.setOnTimer(24 * 60 + 0); + EXPECT_EQ(0, ac.getOnTimer()); + EXPECT_EQ("00:00", ac.timeToString(ac.getOnTimer())); + ac.setOnTimer(25 * 60 + 23); + EXPECT_EQ(1 * 60 + 23, ac.getOnTimer()); + EXPECT_EQ("01:23", ac.timeToString(ac.getOnTimer())); + // Off Timer + ac.enableOffTimer(false); + ac.setOffTimer(0); + EXPECT_EQ(0, ac.getOffTimer()); + EXPECT_EQ("00:00", ac.timeToString(ac.getOffTimer())); + EXPECT_FALSE(ac.isOffTimerEnabled()); + EXPECT_EQ(kWhirlpoolAcCommandOffTimer, ac.getCommand()); + ac.setOffTimer(1); + EXPECT_EQ(1, ac.getOffTimer()); + EXPECT_EQ("00:01", ac.timeToString(ac.getOffTimer())); + ac.enableOffTimer(true); + ac.setOffTimer(12 * 60 + 34); + EXPECT_EQ(12 * 60 + 34, ac.getOffTimer()); + EXPECT_EQ("12:34", ac.timeToString(ac.getOffTimer())); + EXPECT_TRUE(ac.isOffTimerEnabled()); + ac.setOffTimer(7 * 60 + 5); + EXPECT_EQ(7 * 60 + 5, ac.getOffTimer()); + EXPECT_EQ("07:05", ac.timeToString(ac.getOffTimer())); + ac.setOffTimer(23 * 60 + 59); + EXPECT_EQ(23 * 60 + 59, ac.getOffTimer()); + EXPECT_EQ("23:59", ac.timeToString(ac.getOffTimer())); + ac.setOffTimer(24 * 60 + 0); + EXPECT_EQ(0, ac.getOffTimer()); + EXPECT_EQ("00:00", ac.timeToString(ac.getOffTimer())); + ac.setOffTimer(25 * 60 + 23); + EXPECT_EQ(1 * 60 + 23, ac.getOffTimer()); + EXPECT_EQ("01:23", ac.timeToString(ac.getOffTimer())); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetCommand) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); + EXPECT_EQ(0, ac.getCommand()); + ac.setCommand(kWhirlpoolAcCommandFanSpeed); + EXPECT_EQ(kWhirlpoolAcCommandFanSpeed, ac.getCommand()); + ac.setCommand(255); + EXPECT_EQ(255, ac.getCommand()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetPowerToggle) { + IRWhirlpoolAc ac(0); + ac.setCommand(0); + + ac.setPowerToggle(false); + EXPECT_FALSE(ac.getPowerToggle()); + ac.setPowerToggle(true); + EXPECT_TRUE(ac.getPowerToggle()); + ac.setPowerToggle(false); + EXPECT_FALSE(ac.getPowerToggle()); + + // Known state with a power toggle in it. + uint8_t state[21] = {0x83, 0x06, 0x07, 0x82, 0x00, 0x00, 0x93, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x09}; + ac.setRaw(state); + EXPECT_TRUE(ac.getPowerToggle()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetModel) { + IRWhirlpoolAc ac(0); + ac.setTemp(19); + ac.setCommand(0); // Set model shouldn't change the command setting. + + ac.setModel(DG11J191); + EXPECT_EQ(DG11J191, ac.getModel()); + EXPECT_EQ(19, ac.getTemp()); + EXPECT_EQ(0, ac.getCommand()); + ac.setModel(DG11J13A); + EXPECT_EQ(DG11J13A, ac.getModel()); + EXPECT_EQ(19, ac.getTemp()); + ac.setModel(DG11J191); + EXPECT_EQ(DG11J191, ac.getModel()); + EXPECT_EQ(19, ac.getTemp()); + EXPECT_EQ(0, ac.getCommand()); + + // One of the models has a lower min temp. Check that desired temp is kept. + ac.setTemp(16); + ac.setCommand(0); // Set model shouldn't change the command setting. + EXPECT_EQ(16, ac.getTemp()); + EXPECT_EQ(0, ac.getCommand()); + ac.setModel(DG11J13A); + EXPECT_EQ(DG11J13A, ac.getModel()); + EXPECT_EQ(18, ac.getTemp()); + ac.setModel(DG11J191); + EXPECT_EQ(DG11J191, ac.getModel()); + EXPECT_EQ(16, ac.getTemp()); + EXPECT_EQ(0, ac.getCommand()); + + // Known states with different models. + uint8_t state_1[21] = {0x83, 0x06, 0x01, 0x30, 0x00, 0x00, 0x92, + 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, + 0x00, 0x02, 0x00, 0x00, 0x08, 0x00, 0x0A}; + uint8_t state_2[21] = {0x83, 0x06, 0x00, 0x30, 0x00, 0x00, 0x8B, + 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8E, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02}; + + ac.setRaw(state_1); + EXPECT_EQ(DG11J191, ac.getModel()); + ac.setRaw(state_2); + EXPECT_EQ(DG11J13A, ac.getModel()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetSleep) { + IRWhirlpoolAc ac(0); + ac.setFan(kWhirlpoolAcFanAuto); + ac.setCommand(0); + + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + EXPECT_EQ(kWhirlpoolAcCommandSleep, ac.getCommand()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + EXPECT_EQ(kWhirlpoolAcCommandSleep, ac.getCommand()); + EXPECT_EQ(kWhirlpoolAcFanLow, ac.getFan()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + + // Known state with sleep mode in it. + uint8_t state[21] = {0x83, 0x06, 0x0B, 0x73, 0x00, 0x00, 0x90, + 0x9E, 0x00, 0xA0, 0x17, 0x3A, 0x00, 0xFB, + 0x00, 0x03, 0x00, 0x00, 0x08, 0x00, 0x0B}; + ac.setRaw(state); + EXPECT_TRUE(ac.getSleep()); +} + +TEST(TestIRWhirlpoolAcClass, SetAndGetSuper) { + IRWhirlpoolAc ac(0); + ac.setFan(kWhirlpoolAcFanAuto); + ac.setMode(kWhirlpoolAcDry); + ac.setCommand(0); + + ac.setSuper(false); + EXPECT_FALSE(ac.getSuper()); + EXPECT_EQ(kWhirlpoolAcCommandSuper, ac.getCommand()); + ac.setSuper(true); + EXPECT_TRUE(ac.getSuper()); + EXPECT_EQ(kWhirlpoolAcCommandSuper, ac.getCommand()); + EXPECT_EQ(kWhirlpoolAcFanHigh, ac.getFan()); + EXPECT_EQ(kWhirlpoolAcCool, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcMinTemp, ac.getTemp()); + + ac.setSuper(false); + EXPECT_FALSE(ac.getSuper()); + EXPECT_EQ(kWhirlpoolAcFanHigh, ac.getFan()); + EXPECT_EQ(kWhirlpoolAcCool, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcMinTemp, ac.getTemp()); + + // When in heat mode, it should stay in heat mode. + ac.setFan(kWhirlpoolAcFanAuto); + ac.setMode(kWhirlpoolAcHeat); + ac.setSuper(true); + EXPECT_TRUE(ac.getSuper()); + EXPECT_EQ(kWhirlpoolAcCommandSuper, ac.getCommand()); + EXPECT_EQ(kWhirlpoolAcFanHigh, ac.getFan()); + EXPECT_EQ(kWhirlpoolAcHeat, ac.getMode()); + EXPECT_EQ(kWhirlpoolAcMaxTemp, ac.getTemp()); + + // Changing mode/temp/fan/power should cancel super, + ac.setMode(kWhirlpoolAcCool); + EXPECT_FALSE(ac.getSuper()); + ac.setSuper(true); + ac.setTemp(25); + EXPECT_FALSE(ac.getSuper()); + ac.setSuper(true); + ac.setFan(kWhirlpoolAcFanMedium); + EXPECT_FALSE(ac.getSuper()); + ac.setSuper(true); + ac.setPowerToggle(true); + EXPECT_FALSE(ac.getSuper()); + + // Known state with Super mode in it. + uint8_t state[21] = {0x83, 0x06, 0x01, 0x02, 0x00, 0x90, 0x90, + 0x9F, 0x00, 0xA0, 0x17, 0x3A, 0x00, 0x11, + 0x00, 0x04, 0x00, 0x00, 0x08, 0x00, 0x0C}; + ac.setRaw(state); + EXPECT_TRUE(ac.getSuper()); +} + +// Build a known good message from scratch. +TEST(TestIRWhirlpoolAcClass, MessageConstruction) { + // Real example captured from a remote. (ref: RealTimerExample) + uint8_t expectedState[kWhirlpoolAcStateLength] = { + 0x83, 0x06, 0x00, 0x73, 0x00, 0x00, 0x87, 0xA3, 0x08, 0x85, 0x07, + 0x28, 0x00, 0xF5, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x05}; + IRWhirlpoolAc ac(0); + ac.setModel(DG11J13A); + ac.setTemp(25); + ac.setPowerToggle(false); + ac.setMode(kWhirlpoolAcDry); + ac.setFan(kWhirlpoolAcFanAuto); + ac.setSwing(false); + ac.setLight(true); + ac.setClock(7 * 60 + 35); + ac.setOnTimer(7 * 60 + 40); + ac.setOffTimer(8 * 60 + 5); + ac.enableOffTimer(true); + ac.setSleep(false); + ac.setSuper(false); + ac.enableOnTimer(true); + + EXPECT_EQ( + "Model: 1 (DG11J13A), Power toggle: Off, Mode: 3 (DRY), Temp: 25C, " + "Fan: 0 (AUTO), Swing: Off, Light: On, Clock: 07:35, On Timer: 07:40, " + "Off Timer: 08:05, Sleep: Off, Super: Off, Command: 5 (ONTIMER)", + ac.toString()); + EXPECT_STATE_EQ(expectedState, ac.getRaw(), kWhirlpoolAcBits); }