From 0fb37a3afa329487a03af8d333f5f869201009c0 Mon Sep 17 00:00:00 2001 From: David Conran Date: Thu, 25 Jun 2020 18:13:14 +1000 Subject: [PATCH] Add detailed support for Airwell A/C protocol. (#1204) * Add support for controlling: - Operation Mode - Power Toggle - Fan Speed - Temperature * Enforce a fan speed of Low when in Dry mode. * Common A/C API support added. * Unit tests Fixes #1202 --- src/IRac.cpp | 58 ++++++++++- src/IRac.h | 6 ++ src/ir_Airwell.cpp | 217 +++++++++++++++++++++++++++++++++++++-- src/ir_Airwell.h | 97 +++++++++++++++++ test/IRac_test.cpp | 24 +++++ test/ir_Airwell_test.cpp | 150 ++++++++++++++++++++++++++- 6 files changed, 544 insertions(+), 8 deletions(-) create mode 100644 src/ir_Airwell.h diff --git a/src/IRac.cpp b/src/IRac.cpp index fda16aba..bf17067b 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -16,6 +16,7 @@ #include "IRremoteESP8266.h" #include "IRtext.h" #include "IRutils.h" +#include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" #include "ir_Carrier.h" @@ -132,6 +133,9 @@ stdAc::state_t IRac::getStatePrev(void) { return _prev; } /// @return true if the protocol is supported by this class, otherwise false. bool IRac::isProtocolSupported(const decode_type_t protocol) { switch (protocol) { +#if SEND_AIRWELL + case decode_type_t::AIRWELL: +#endif #if SEND_AMCOR case decode_type_t::AMCOR: #endif @@ -269,6 +273,34 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { } } +#if SEND_AIRWELL +/// Send an Airwell A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRAirwellAc 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. +void IRac::airwell(IRAirwellAc *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan) { + ac->begin(); + ac->setPowerToggle(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No Swing setting available. + // No Quiet setting available. + // No Light setting available. + // No Filter setting available. + // No Turbo setting available. + // No Economy setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + ac->send(); +} +#endif // SEND_AIRWELL + #if SEND_AMCOR /// Send an Amcor A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRAmcorAc object to use. @@ -1892,6 +1924,7 @@ stdAc::state_t IRac::handleToggles(const stdAc::state_t desired, else result.swingv = stdAc::swingv_t::kOff; // No change, so no toggle. break; + case decode_type_t::AIRWELL: case decode_type_t::DAIKIN64: case decode_type_t::WHIRLPOOL_AC: result.power = desired.power ^ prev->power; @@ -1959,6 +1992,14 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { stdAc::state_t send = this->handleToggles(this->cleanState(desired), prev); // Per vendor settings & setup. switch (send.protocol) { +#if SEND_AIRWELL + case AIRWELL: + { + IRAirwellAc ac(_pin, _inverted, _modulation); + airwell(&ac, send.power, send.mode, degC, send.fanspeed); + break; + } +#endif // SEND_AIRWELL #if SEND_AMCOR case AMCOR: { @@ -2715,6 +2756,13 @@ namespace IRAcUtils { /// An empty string if we can't. String resultAcToString(const decode_results * const result) { switch (result->decode_type) { +#if DECODE_AIRWELL + case decode_type_t::AIRWELL: { + IRAirwellAc ac(kGpioUnused); + ac.setRaw(result->value); // AIRWELL uses value instead of state. + return ac.toString(); + } +#endif // DECODE_AIRWELL #if DECODE_AMCOR case decode_type_t::AMCOR: { IRAmcorAc ac(0); @@ -2724,7 +2772,7 @@ namespace IRAcUtils { #endif // DECODE_AMCOR #if DECODE_ARGO case decode_type_t::ARGO: { - IRArgoAC ac(0); + IRArgoAC ac(kGpioUnused); ac.setRaw(result->state); return ac.toString(); } @@ -3040,6 +3088,14 @@ namespace IRAcUtils { ) { if (decode == NULL || result == NULL) return false; // Safety check. switch (decode->decode_type) { +#if DECODE_AIRWELL + case decode_type_t::AIRWELL: { + IRAirwellAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_AIRWELL #if DECODE_AMCOR case decode_type_t::AMCOR: { IRAmcorAc ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 7db64ffd..5c59741d 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -7,6 +7,7 @@ #include #endif #include "IRremoteESP8266.h" +#include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" #include "ir_Carrier.h" @@ -98,6 +99,11 @@ class IRac { bool _inverted; ///< IR LED is lit when GPIO is LOW (true) or HIGH (false)? bool _modulation; ///< Is frequency modulation to be used? stdAc::state_t _prev; ///< The state we expect the device to currently be in. +#if SEND_AIRWELL + void airwell(IRAirwellAc *ac, + const bool on, const stdAc::opmode_t mode, const float degrees, + const stdAc::fanspeed_t fan); +#endif // SEND_AIRWELL #if SEND_AMCOR void amcor(IRAmcorAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, diff --git a/src/ir_Airwell.cpp b/src/ir_Airwell.cpp index 2bf0a2db..106e9c7b 100644 --- a/src/ir_Airwell.cpp +++ b/src/ir_Airwell.cpp @@ -1,23 +1,28 @@ // Copyright 2020 David Conran - +#include "ir_Airwell.h" +#include #include "IRrecv.h" #include "IRsend.h" +#include "IRtext.h" +#include "IRutils.h" /// @file /// @brief Airwell "Manchester code" based protocol. /// Some other Airwell products use the COOLIX protocol. -// Supports: -// Brand: Airwell, Model: RC08W remote -// Brand: Airwell, Model: RC04 remote -// Brand: Airwell, Model: DLS 21 DCI R410 AW A/C - const uint8_t kAirwellOverhead = 4; const uint16_t kAirwellHalfClockPeriod = 950; // uSeconds const uint16_t kAirwellHdrMark = 3 * kAirwellHalfClockPeriod; // uSeconds const uint16_t kAirwellHdrSpace = 3 * kAirwellHalfClockPeriod; // uSeconds const uint16_t kAirwellFooterMark = 5 * kAirwellHalfClockPeriod; // uSeconds +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::setBit; +using irutils::setBits; + #if SEND_AIRWELL /// Send an Airwell Manchester Code formatted message. /// Status: BETA / Appears to be working. @@ -74,3 +79,203 @@ bool IRrecv::decodeAirwell(decode_results *results, uint16_t offset, return true; } #endif + +/// Class constructor +/// @param[in] pin GPIO to be used when sending. +/// @param[in] inverted Is the output signal to be inverted? +/// @param[in] use_modulation Is frequency modulation to be used? +IRAirwellAc::IRAirwellAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Set up hardware to be able to send a message. +void IRAirwellAc::begin(void) { _irsend.begin(); } + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A copy of the internal state. +uint64_t IRAirwellAc::getRaw(void) { + return remote_state; +} + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +void IRAirwellAc::setRaw(const uint64_t state) { + remote_state = state; +} + +#if SEND_AIRWELL +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRAirwellAc::send(const uint16_t repeat) { + _irsend.sendAirwell(getRaw(), kAirwellBits, repeat); +} +#endif // SEND_AIRWELL + +/// Reset the internals of the object to a known good state. +void IRAirwellAc::stateReset(void) { + remote_state = kAirwellKnownGoodState; +} + +/// Turn on/off the Power Airwell setting. +/// @param[in] on The desired setting state. +void IRAirwellAc::setPowerToggle(const bool on) { + setBit(&remote_state, kAirwellPowerToggleBit, on); +} + +/// Get the power toggle setting from the internal state. +/// @return A boolean indicating the setting. +bool IRAirwellAc::getPowerToggle(void) { + return GETBIT64(remote_state, kAirwellPowerToggleBit); +} + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRAirwellAc::getMode(void) { + return GETBITS64(remote_state, kAirwellModeOffset, kAirwellModeSize); +} + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRAirwellAc::setMode(const uint8_t mode) { + switch (mode) { + case kAirwellFan: + case kAirwellCool: + case kAirwellHeat: + case kAirwellDry: + case kAirwellAuto: + setBits(&remote_state, kAirwellModeOffset, kAirwellModeSize, mode); + break; + default: + setMode(kAirwellAuto); + } + setFan(getFan()); // Ensure the fan is at the correct speed for the new mode. +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivilant of the enum. +uint8_t IRAirwellAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kAirwellCool; + case stdAc::opmode_t::kHeat: return kAirwellHeat; + case stdAc::opmode_t::kDry: return kAirwellDry; + case stdAc::opmode_t::kFan: return kAirwellFan; + default: return kAirwellAuto; + } +} + +/// Convert a native mode into its stdAc equivilant. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivilant of the native setting. +stdAc::opmode_t IRAirwellAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kAirwellCool: return stdAc::opmode_t::kCool; + case kAirwellHeat: return stdAc::opmode_t::kHeat; + case kAirwellDry: return stdAc::opmode_t::kDry; + case kAirwellFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +/// @note The speed is locked to Low when in Dry mode. +void IRAirwellAc::setFan(const uint8_t speed) { + setBits(&remote_state, kAirwellFanOffset, kAirwellFanSize, + (getMode() == kAirwellDry) ? kAirwellFanLow + : std::min(speed, kAirwellFanAuto)); +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRAirwellAc::getFan(void) { + return GETBITS64(remote_state, kAirwellFanOffset, kAirwellFanSize); +} + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivilant of the enum. +uint8_t IRAirwellAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: + return kAirwellFanLow; + case stdAc::fanspeed_t::kMedium: + return kAirwellFanMedium; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: + return kAirwellFanHigh; + default: + return kAirwellFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivilant. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivilant of the native setting. +stdAc::fanspeed_t IRAirwellAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kAirwellFanHigh: return stdAc::fanspeed_t::kMax; + case kAirwellFanMedium: return stdAc::fanspeed_t::kMedium; + case kAirwellFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRAirwellAc::setTemp(const uint8_t degrees) { + uint8_t temp = std::max(kAirwellMinTemp, degrees); + temp = std::min(kAirwellMaxTemp, temp); + setBits(&remote_state, kAirwellTempOffset, kAirwellTempSize, + temp - kAirwellMinTemp + 1); +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRAirwellAc::getTemp(void) { + return GETBITS64(remote_state, kAirwellTempOffset, + kAirwellTempSize) + kAirwellMinTemp - 1; +} + +/// Convert the current internal state into its stdAc::state_t equivilant. +/// @return The stdAc equivilant of the native settings. +stdAc::state_t IRAirwellAc::toCommon(void) { + stdAc::state_t result; + result.protocol = decode_type_t::AIRWELL; + result.power = getPowerToggle(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + // Not supported. + result.model = -1; + result.turbo = false; + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.light = false; + result.filter = false; + result.econo = false; + result.quiet = false; + result.clean = false; + result.beep = false; + result.sleep = -1; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRAirwellAc::toString(void) { + String result = ""; + result.reserve(70); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPowerToggle(), kPowerToggleStr, false); + result += addModeToString(getMode(), kAirwellAuto, kAirwellCool, + kAirwellHeat, kAirwellDry, kAirwellFan); + result += addFanToString(getFan(), kAirwellFanHigh, kAirwellFanLow, + kAirwellFanAuto, kAirwellFanAuto, + kAirwellFanMedium); + result += addTempToString(getTemp()); + return result; +} diff --git a/src/ir_Airwell.h b/src/ir_Airwell.h new file mode 100644 index 00000000..3ffbd21c --- /dev/null +++ b/src/ir_Airwell.h @@ -0,0 +1,97 @@ +// Copyright 2020 David Conran + +/// @file +/// @brief Airwell "Manchester code" based protocol. +/// Some other Airwell products use the COOLIX protocol. + +// Supports: +// Brand: Airwell, Model: RC08W remote +// Brand: Airwell, Model: RC04 remote +// Brand: Airwell, Model: DLS 21 DCI R410 AW A/C + +#ifndef IR_AIRWELL_H_ +#define IR_AIRWELL_H_ + +#define __STDC_LIMIT_MACROS +#include +#ifndef UNIT_TEST +#include +#endif +#include "IRremoteESP8266.h" +#include "IRsend.h" +#ifdef UNIT_TEST +#include "IRsend_test.h" +#endif + + +// Constants +const uint64_t kAirwellKnownGoodState = 0x140500002; // Mode Fan, Speed 1, 25C +// Temperature +const uint8_t kAirwellMinTemp = 16; // Celsius +const uint8_t kAirwellMaxTemp = 30; // Celsius +const uint8_t kAirwellTempSize = 4; // Bits +const uint8_t kAirwellTempOffset = 19; // 0b1111 << 19 +// Fan +const uint8_t kAirwellFanSize = 2; // Bits +const uint8_t kAirwellFanOffset = 28; // 0b11 << 28 +const uint8_t kAirwellFanLow = 0; // 0b00 +const uint8_t kAirwellFanMedium = 1; // 0b01 +const uint8_t kAirwellFanHigh = 2; // 0b10 +const uint8_t kAirwellFanAuto = 3; // 0b11 +// Modes +const uint8_t kAirwellModeSize = 3; // Bits +const uint8_t kAirwellModeOffset = 30; // 0b111 << 30 +const uint8_t kAirwellCool = 1; // 0b001 +const uint8_t kAirwellHeat = 2; // 0b010 +const uint8_t kAirwellAuto = 3; // 0b011 +const uint8_t kAirwellDry = 4; // 0b100 +const uint8_t kAirwellFan = 5; // 0b101 +// Power +const uint8_t kAirwellPowerToggleBit = 33; // 0b1 << 33 + + +// Classes +/// Class for handling detailed Airwell A/C messages. +class IRAirwellAc { + public: + explicit IRAirwellAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(); +#if SEND_AIRWELL + void send(const uint16_t repeat = kAirwellMinRepeats); + /// Run the calibration to calculate uSec timing offsets for this platform. + /// @return The uSec timing offset needed per modulation of the IR Led. + /// @note This will produce a 65ms IR signal pulse at 38kHz. + /// Only ever needs to be run once per object instantiation, if at all. + int8_t calibrate(void) { return _irsend.calibrate(); } +#endif // SEND_AIRWELL + void begin(); + void setPowerToggle(const bool on); + bool getPowerToggle(); + 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(); + uint64_t getRaw(); + void setRaw(const uint64_t state); + uint8_t convertMode(const stdAc::opmode_t mode); + uint8_t convertFan(const stdAc::fanspeed_t speed); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + stdAc::state_t toCommon(void); + String toString(); +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif + uint64_t remote_state; // The state of the IR remote in native IR code form. + void checksum(void); +}; +#endif // IR_AIRWELL_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index a0f97566..b3c20194 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1,6 +1,7 @@ // Copyright 2019 David Conran #include +#include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" #include "ir_Carrier.h" @@ -39,6 +40,29 @@ // Tests for IRac class. +TEST(TestIRac, Airwell) { + IRAirwellAc ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + char expected[] = + "Power Toggle: On, Mode: 3 (Auto), Fan: 1 (Medium), Temp: 18C"; + + ac.begin(); + irac.airwell(&ac, + true, // Power + stdAc::opmode_t::kAuto, // Mode + 18, // Celsius + stdAc::fanspeed_t::kMedium); // Fan speed + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(AIRWELL, ac._irsend.capture.decode_type); + ASSERT_EQ(kAirwellBits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); +} + TEST(TestIRac, Amcor) { IRAmcorAc ac(0); IRac irac(0); diff --git a/test/ir_Airwell_test.cpp b/test/ir_Airwell_test.cpp index 09300bac..e5f28d4d 100644 --- a/test/ir_Airwell_test.cpp +++ b/test/ir_Airwell_test.cpp @@ -1,5 +1,6 @@ // Copyright 2020 David Conran +#include "ir_Airwell.h" #include "IRac.h" #include "IRrecv.h" #include "IRrecv_test.h" @@ -44,6 +45,9 @@ TEST(TestDecodeAirwell, RealExample) { EXPECT_EQ(0x2B0D0181B, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "Power Toggle: On, Mode: 2 (Heat), Fan: 3 (Auto), Temp: 25C", + IRAcUtils::resultAcToString(&irsend.capture)); const uint16_t rawData_2[175] = { 2862, 3892, @@ -76,6 +80,9 @@ TEST(TestDecodeAirwell, RealExample) { EXPECT_EQ(0x270F8181B, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "Power Toggle: On, Mode: 1 (Cool), Fan: 3 (Auto), Temp: 30C", + IRAcUtils::resultAcToString(&irsend.capture)); } TEST(TestDecodeAirwell, SyntheticExample) { @@ -192,6 +199,9 @@ TEST(TestDecodeAirwell, RealExample2) { EXPECT_EQ(0xB0C0181B, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "Power Toggle: Off, Mode: 2 (Heat), Fan: 3 (Auto), Temp: 23C", + IRAcUtils::resultAcToString(&irsend.capture)); // Resend it as a synthetic to see if it decodes to the same value. irsend.reset(); @@ -210,7 +220,7 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("AIRWELL", typeToString(decode_type_t::AIRWELL)); ASSERT_EQ(decode_type_t::AIRWELL, strToDecodeType("AIRWELL")); ASSERT_FALSE(hasACState(decode_type_t::AIRWELL)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::AIRWELL)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::AIRWELL)); ASSERT_EQ(kAirwellBits, IRsend::defaultBits(decode_type_t::AIRWELL)); ASSERT_EQ(kAirwellMinRepeats, IRsend::minRepeats(decode_type_t::AIRWELL)); } @@ -250,4 +260,142 @@ TEST(TestDecodeAirwell, RealExample3) { EXPECT_EQ(0x60080002, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "Power Toggle: Off, Mode: 1 (Cool), Fan: 2 (High), Temp: 16C", + IRAcUtils::resultAcToString(&irsend.capture)); +} + +// Tests for IRAirwellAc class. + +TEST(TestAirwellAcClass, PowerToggle) { + IRAirwellAc ac(kGpioUnused); + ac.begin(); + + ac.setPowerToggle(true); + EXPECT_TRUE(ac.getPowerToggle()); + ac.setPowerToggle(false); + EXPECT_FALSE(ac.getPowerToggle()); + ac.setPowerToggle(true); + EXPECT_TRUE(ac.getPowerToggle()); +} + +TEST(TestAirwellAcClass, Temperature) { + IRAirwellAc ac(kGpioUnused); + ac.begin(); + + ac.setTemp(0); + EXPECT_EQ(kAirwellMinTemp, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kAirwellMaxTemp, ac.getTemp()); + + ac.setTemp(kAirwellMinTemp); + EXPECT_EQ(kAirwellMinTemp, ac.getTemp()); + + ac.setTemp(kAirwellMaxTemp); + EXPECT_EQ(kAirwellMaxTemp, ac.getTemp()); + + ac.setTemp(kAirwellMinTemp - 1); + EXPECT_EQ(kAirwellMinTemp, ac.getTemp()); + + ac.setTemp(kAirwellMaxTemp + 1); + EXPECT_EQ(kAirwellMaxTemp, ac.getTemp()); + + ac.setTemp(17); + EXPECT_EQ(17, ac.getTemp()); + + ac.setTemp(21); + EXPECT_EQ(21, ac.getTemp()); + + ac.setTemp(25); + EXPECT_EQ(25, ac.getTemp()); + + ac.setTemp(29); + EXPECT_EQ(29, ac.getTemp()); +} + +TEST(TestAirwellAcClass, OperatingMode) { + IRAirwellAc ac(kGpioUnused); + ac.begin(); + + ac.setMode(kAirwellAuto); + EXPECT_EQ(kAirwellAuto, ac.getMode()); + + ac.setMode(kAirwellCool); + EXPECT_EQ(kAirwellCool, ac.getMode()); + + ac.setMode(kAirwellHeat); + EXPECT_EQ(kAirwellHeat, ac.getMode()); + + ac.setMode(kAirwellDry); + EXPECT_EQ(kAirwellDry, ac.getMode()); + + ac.setMode(kAirwellFan); + EXPECT_EQ(kAirwellFan, ac.getMode()); + + ac.setMode(kAirwellFan + 1); + EXPECT_EQ(kAirwellAuto, ac.getMode()); + + ac.setMode(255); + EXPECT_EQ(kAirwellAuto, ac.getMode()); +} + +TEST(TestAirwellAcClass, FanSpeed) { + IRAirwellAc ac(0); + ac.begin(); + + ac.setFan(0); + EXPECT_EQ(kAirwellFanLow, ac.getFan()); + + ac.setFan(255); + EXPECT_EQ(kAirwellFanAuto, ac.getFan()); + + ac.setFan(kAirwellFanHigh); + EXPECT_EQ(kAirwellFanHigh, ac.getFan()); + + ac.setFan(kAirwellFanHigh + 2); + EXPECT_EQ(kAirwellFanAuto, ac.getFan()); + + ac.setFan(kAirwellFanHigh - 1); + EXPECT_EQ(kAirwellFanHigh - 1, ac.getFan()); + + ac.setFan(1); + EXPECT_EQ(1, ac.getFan()); + + ac.setFan(1); + EXPECT_EQ(1, ac.getFan()); + + ac.setFan(3); + EXPECT_EQ(3, ac.getFan()); +} + +// Test human readable output. +TEST(TestAirwellAcClass, HumanReadable) { + IRAirwellAc ac(kGpioUnused); + EXPECT_EQ( + "Power Toggle: Off, Mode: 5 (Fan), Fan: 0 (Low), Temp: 25C", + ac.toString()); + ac.setPowerToggle(true); + ac.setMode(kAirwellHeat); + ac.setTemp(30); + ac.setFan(kAirwellFanAuto); + EXPECT_EQ( + "Power Toggle: On, Mode: 2 (Heat), Fan: 3 (Auto), Temp: 30C", + ac.toString()); +} + +TEST(TestAirwellAcClass, ReconstructKnownState) { + IRAirwellAc ac(kGpioUnused); + const uint64_t expected = 0x240380002; + ac.begin(); + ac.stateReset(); + ASSERT_NE(expected, ac.getRaw()); + ac.setPowerToggle(true); + ac.setMode(kAirwellCool); + ac.setTemp(22); + ac.setFan(kAirwellFanLow); + EXPECT_EQ(expected, ac.getRaw()); + EXPECT_EQ( + "Power Toggle: On, Mode: 1 (Cool), Fan: 0 (Low), Temp: 22C", + ac.toString()); }