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
This commit is contained in:
David Conran
2020-06-25 18:13:14 +10:00
committed by GitHub
parent 576e71ba09
commit 0fb37a3afa
6 changed files with 544 additions and 8 deletions

View File

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

View File

@@ -7,6 +7,7 @@
#include <Arduino.h>
#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,

View File

@@ -1,23 +1,28 @@
// Copyright 2020 David Conran
#include "ir_Airwell.h"
#include <algorithm>
#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;
}

97
src/ir_Airwell.h Normal file
View File

@@ -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 <stdint.h>
#ifndef UNIT_TEST
#include <Arduino.h>
#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_

View File

@@ -1,6 +1,7 @@
// Copyright 2019 David Conran
#include <string>
#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);

View File

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