diff --git a/src/IRac.cpp b/src/IRac.cpp index 557e5593e..43cc4ef55 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -16,6 +16,7 @@ #include "IRremoteESP8266.h" #include "IRtext.h" #include "IRutils.h" +#include "ir_Airton.h" #include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" @@ -152,9 +153,12 @@ 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_AIRTON + case decode_type_t::AIRTON: +#endif // SEND_AIRTON #if SEND_AIRWELL case decode_type_t::AIRWELL: -#endif +#endif // SEND_AIRWELL #if SEND_AMCOR case decode_type_t::AMCOR: #endif @@ -326,6 +330,44 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { } } +#if SEND_AIRTON +/// Send an Airton 56-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRAirtonAc 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/powerful mode. +/// @param[in] light Turn on the LED/Display mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] filter Turn on the (ion/pollen/health/etc) filter mode. +/// @param[in] sleep Nr. of minutes for sleep mode. +/// @note -1 is Off, >= 0 is on. +void IRac::airton(IRAirtonAc *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 bool econo, const bool filter, + const int16_t sleep) { + ac->begin(); + ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + ac->setSwingV(swingv != stdAc::swingv_t::kOff); + // No Quiet setting available. + ac->setLight(light); + ac->setHealth(filter); + ac->setTurbo(turbo); + ac->setEcono(econo); + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); // Convert to a boolean. + ac->send(); +} +#endif // SEND_AIRTON + #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. @@ -2603,6 +2645,16 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { #endif // (SEND_LG || SEND_SHARP_AC) // Per vendor settings & setup. switch (send.protocol) { +#if SEND_AIRTON + case AIRTON: + { + IRAirtonAc ac(_pin, _inverted, _modulation); + airton(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.turbo, send.light, send.econo, send.filter, + send.sleep); + break; + } +#endif // SEND_AIRTON #if SEND_AIRWELL case AIRWELL: { @@ -3507,6 +3559,13 @@ namespace IRAcUtils { /// An empty string if we can't. String resultAcToString(const decode_results * const result) { switch (result->decode_type) { +#if DECODE_AIRTON + case decode_type_t::AIRTON: { + IRAirtonAc ac(kGpioUnused); + ac.setRaw(result->value); // AIRTON uses value instead of state. + return ac.toString(); + } +#endif // DECODE_AIRTON #if DECODE_AIRWELL case decode_type_t::AIRWELL: { IRAirwellAc ac(kGpioUnused); @@ -3931,6 +3990,14 @@ namespace IRAcUtils { ) { if (decode == NULL || result == NULL) return false; // Safety check. switch (decode->decode_type) { +#if DECODE_AIRTON + case decode_type_t::AIRTON: { + IRAirtonAc ac(kGpioUnused); + ac.setRaw(decode->value); // Uses value instead of state. + *result = ac.toCommon(); + break; + } +#endif // DECODE_AIRTON #if DECODE_AIRWELL case decode_type_t::AIRWELL: { IRAirwellAc ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index afa4bcee0..2ac49baed 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -7,6 +7,7 @@ #include #endif #include "IRremoteESP8266.h" +#include "ir_Airton.h" #include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" @@ -109,6 +110,14 @@ 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_AIRTON + void airton(IRAirtonAc *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 bool econo, const bool filter, + const int16_t sleep = -1); +#endif // SEND_AIRTON #if SEND_AIRWELL void airwell(IRAirwellAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, diff --git a/src/IRutils.cpp b/src/IRutils.cpp index d3a83c507..e1f889966 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -1049,6 +1049,21 @@ namespace irutils { return nibbleonly ? sum & 0xF : sum; } + /// Sum all the bytes together in an integer. + /// @param[in] data The integer to be summed. + /// @param[in] count The number of bytes to sum. Starts from LSB. Max of 8. + /// @param[in] init Starting value of the calculation to use. (Default is 0) + /// @param[in] byteonly true, the result is 8 bits. false, it's 16 bits. + /// @return The 8/16-bit calculated result of all the bytes and init value. + uint16_t sumBytes(const uint64_t data, const uint8_t count, + const uint8_t init, const bool byteonly) { + uint16_t sum = init; + uint64_t copy = data; + const uint8_t nrofbytes = (count < 8) ? count : (64 / 8); + for (uint8_t i = 0; i < nrofbytes; i++, copy >>= 8) sum += (copy & 0xFF); + return byteonly ? sum & 0xFF : sum; + } + /// Convert a byte of Binary Coded Decimal(BCD) into an Integer. /// @param[in] bcd The BCD value. /// @return A normal Integer value. diff --git a/src/IRutils.h b/src/IRutils.h index 61fe8b269..a5dcde043 100644 --- a/src/IRutils.h +++ b/src/IRutils.h @@ -94,6 +94,8 @@ namespace irutils { const uint8_t init = 0); uint8_t sumNibbles(const uint64_t data, const uint8_t count = 16, const uint8_t init = 0, const bool nibbleonly = true); + uint16_t sumBytes(const uint64_t data, const uint8_t count = 8, + const uint8_t init = 0, const bool byteonly = true); uint8_t bcdToUint8(const uint8_t bcd); uint8_t uint8ToBcd(const uint8_t integer); bool getBit(const uint64_t data, const uint8_t position, diff --git a/src/ir_Airton.cpp b/src/ir_Airton.cpp index 507894f37..83ad95ef7 100644 --- a/src/ir_Airton.cpp +++ b/src/ir_Airton.cpp @@ -3,12 +3,11 @@ /// @brief Support for Airton protocol /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670 -// Supports: -// Brand: Airton, Model: SMVH09B-2A2A3NH ref. 409730 A/C -// Brand: Airton, Model: RD1A1 remote - +#include "ir_Airton.h" +#include #include "IRrecv.h" #include "IRsend.h" +#include "IRtext.h" #include "IRutils.h" const uint16_t kAirtonHdrMark = 6630; @@ -18,6 +17,12 @@ const uint16_t kAirtonOneSpace = 1260; const uint16_t kAirtonZeroSpace = 430; const uint16_t kAirtonFreq = 38000; // Hz. (Just a guess) +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::sumBytes; + #if SEND_AIRTON // Function should be safe up to 64 bits. /// Send a Airton formatted message. @@ -59,7 +64,8 @@ bool IRrecv::decodeAirton(decode_results *results, uint16_t offset, kAirtonBitMark, kAirtonZeroSpace, kAirtonBitMark, kDefaultMessageGap, true, kUseDefTol, kMarkExcess, false)) return false; - + // Compliance + if (strict && !IRAirtonAc::validChecksum(results->value)) return false; // Success results->decode_type = decode_type_t::AIRTON; results->bits = nbits; @@ -68,3 +74,289 @@ bool IRrecv::decodeAirton(decode_results *results, uint16_t offset, return true; } #endif // DECODE_AIRTON + +/// 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? +IRAirtonAc::IRAirtonAc(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 IRAirtonAc::begin(void) { _irsend.begin(); } + +#if SEND_AIRTON +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRAirtonAc::send(const uint16_t repeat) { + _irsend.sendAirton(getRaw(), kAirtonBits, repeat); +} +#endif // SEND_AIRTON + +/// Calculate the checksum for the supplied state. +/// @param[in] state The source state to generate the checksum from. +/// @return The checksum value. +uint8_t IRAirtonAc::calcChecksum(const uint64_t state) { + return (uint8_t)(0x7F - sumBytes(state, 6)) ^ 0x2C; +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The value to verify the checksum of. +/// @return A boolean indicating if it's checksum is valid. +bool IRAirtonAc::validChecksum(const uint64_t state) { + AirtonProtocol p; + p.raw = state; + return p.Sum == IRAirtonAc::calcChecksum(state); +} + +/// Update the checksum value for the internal state. +void IRAirtonAc::checksum(void) { _.Sum = IRAirtonAc::calcChecksum(_.raw); } + +/// Reset the internals of the object to a known good state. +void IRAirtonAc::stateReset(void) { setRaw(0x11D3); } + +/// Get the raw state of the object, suitable to be sent with the appropriate +/// IRsend object method. +/// @return A copy to the internal state. +uint64_t IRAirtonAc::getRaw(void) { + checksum(); // Ensure correct bit array before returning + return _.raw; +} + +/// Set the raw state of the object. +/// @param[in] state The raw state from the native IR message. +void IRAirtonAc::setRaw(const uint64_t state) { _.raw = state; } + + +/// Set the internal state to have the power on. +void IRAirtonAc::on(void) { setPower(true); } + +/// Set the internal state to have the power off. +void IRAirtonAc::off(void) { setPower(false); } + +/// Set the internal state to have the desired power. +/// @param[in] on The desired power state. +void IRAirtonAc::setPower(const bool on) { + _.Power = on; + setMode(getMode()); // Re-do the mode incase we need to do something special. +} + +/// Get the power setting from the internal state. +/// @return A boolean indicating the power setting. +bool IRAirtonAc::getPower(void) const { return _.Power; } + +/// Get the current operation mode setting. +/// @return The current operation mode. +uint8_t IRAirtonAc::getMode(void) const { return _.Mode; } + +/// Set the desired operation mode. +/// @param[in] mode The desired operation mode. +void IRAirtonAc::setMode(const uint8_t mode) { + // Changing the mode always removes the sleep setting. + if (mode != _.Mode) setSleep(false); + // Set the actual mode. + _.Mode = (mode > kAirtonHeat) ? kAirtonAuto : mode; + // Handle special settings for each mode. + switch (_.Mode) { + case kAirtonAuto: + setTemp(25); // Auto has a fixed temp. + _.NotAutoOn = !getPower(); + break; + case kAirtonHeat: + // When powered on and in Heat mode, set a special bit. + _.HeatOn = getPower(); + // FALL-THRU + default: + _.NotAutoOn = true; + } + // Reset the economy setting if we need to. + setEcono(getEcono()); +} + +/// Convert a stdAc::opmode_t enum into its native mode. +/// @param[in] mode The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirtonAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kCool: return kAirtonCool; + case stdAc::opmode_t::kHeat: return kAirtonHeat; + case stdAc::opmode_t::kDry: return kAirtonDry; + case stdAc::opmode_t::kFan: return kAirtonFan; + default: return kAirtonAuto; + } +} + +/// Convert a native mode into its stdAc equivalent. +/// @param[in] mode The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::opmode_t IRAirtonAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kAirtonCool: return stdAc::opmode_t::kCool; + case kAirtonHeat: return stdAc::opmode_t::kHeat; + case kAirtonDry: return stdAc::opmode_t::kDry; + case kAirtonFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kAuto; + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRAirtonAc::setTemp(const uint8_t degrees) { + uint8_t temp = std::max(kAirtonMinTemp, degrees); + temp = std::min(kAirtonMaxTemp, temp); + if (_.Mode == kAirtonAuto) temp = kAirtonMaxTemp; // Auto has a fixed temp. + _.Temp = temp - kAirtonMinTemp; +} + +/// Get the current temperature setting. +/// @return Get current setting for temp. in degrees celsius. +uint8_t IRAirtonAc::getTemp(void) const { return _.Temp + kAirtonMinTemp; } + + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRAirtonAc::setFan(const uint8_t speed) { + _.Fan = (speed > kAirtonFanMax) ? kAirtonFanAuto : speed; +} + +/// Get the current fan speed setting. +/// @return The current fan speed. +uint8_t IRAirtonAc::getFan(void) const { return _.Fan; } + +/// Convert a stdAc::fanspeed_t enum into it's native speed. +/// @param[in] speed The enum to be converted. +/// @return The native equivalent of the enum. +uint8_t IRAirtonAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: return kAirtonFanMin; + case stdAc::fanspeed_t::kLow: return kAirtonFanLow; + case stdAc::fanspeed_t::kMedium: return kAirtonFanMed; + case stdAc::fanspeed_t::kHigh: return kAirtonFanHigh; + case stdAc::fanspeed_t::kMax: return kAirtonFanMax; + default: return kAirtonFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivalent. +/// @param[in] speed The native setting to be converted. +/// @return The stdAc equivalent of the native setting. +stdAc::fanspeed_t IRAirtonAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kAirtonFanMax: return stdAc::fanspeed_t::kMax; + case kAirtonFanHigh: return stdAc::fanspeed_t::kHigh; + case kAirtonFanMed: return stdAc::fanspeed_t::kMedium; + case kAirtonFanLow: return stdAc::fanspeed_t::kLow; + case kAirtonFanMin: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setSwingV(const bool on) { _.SwingV = on; } + +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getSwingV(void) const { return _.SwingV; } + +/// Set the Light/LED/Display setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setLight(const bool on) { _.Light = on; } + +/// Get the Light/LED/Display setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getLight(void) const { return _.Light; } + +/// Set the Economy setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Only available in Cool mode. +void IRAirtonAc::setEcono(const bool on) { + _.Econo = on && (getMode() == kAirtonCool); +} + +/// Get the Economy setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getEcono(void) const { return _.Econo; } + +/// Set the Turbo setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setTurbo(const bool on) { + _.Turbo = on; + // Pressing the turbo button sets the fan to max as well. + if (on) setFan(kAirtonFanMax); +} + +/// Get the Turbo setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getTurbo(void) const { return _.Turbo; } + +/// Set the Sleep setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +/// @note Sleep not available in fan or auto mode. +void IRAirtonAc::setSleep(const bool on) { + switch (getMode()) { + case kAirtonAuto: + case kAirtonFan: _.Sleep = false; break; + default: _.Sleep = on; + } +} + +/// Get the Sleep setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getSleep(void) const { return _.Sleep; } + +/// Set the Health/Filter setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRAirtonAc::setHealth(const bool on) { _.Health = on; } + +/// Get the Health/Filter setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRAirtonAc::getHealth(void) const { return _.Health; } + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRAirtonAc::toCommon(void) const { + stdAc::state_t result; + result.protocol = decode_type_t::AIRTON; + result.power = getPower(); + result.mode = toCommonMode(getMode()); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.econo = getEcono(); + result.turbo = getTurbo(); + result.filter = getHealth(); + result.light = getLight(); + result.sleep = getSleep() ? 0 : -1; + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.clean = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the current internal state into a human readable string. +/// @return A human readable string. +String IRAirtonAc::toString(void) const { + String result = ""; + result.reserve(135); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(getPower(), kPowerStr, false); + result += addModeToString(_.Mode, kAirtonAuto, kAirtonCool, + kAirtonHeat, kAirtonDry, kAirtonFan); + result += addFanToString(_.Fan, kAirtonFanHigh, kAirtonFanLow, + kAirtonFanAuto, kAirtonFanMin, kAirtonFanMed, + kAirtonFanMax); + result += addTempToString(getTemp()); + result += addBoolToString(getSwingV(), kSwingVStr); + result += addBoolToString(getEcono(), kEconoStr); + result += addBoolToString(getTurbo(), kTurboStr); + result += addBoolToString(getLight(), kLightStr); + result += addBoolToString(getHealth(), kHealthStr); + result += addBoolToString(getSleep(), kSleepStr); + return result; +} diff --git a/src/ir_Airton.h b/src/ir_Airton.h new file mode 100644 index 000000000..9b5e89f3f --- /dev/null +++ b/src/ir_Airton.h @@ -0,0 +1,134 @@ +// Copyright 2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Airton protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1670 + +// Supports: +// Brand: Airton, Model: SMVH09B-2A2A3NH ref. 409730 A/C +// Brand: Airton, Model: RD1A1 remote + +#ifndef IR_AIRTON_H_ +#define IR_AIRTON_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 + +/// Native representation of a Airton 56 A/C message. +/// @see https://docs.google.com/spreadsheets/d/1Kpq7WCkh85heLnTQGlwUfCR6eeu_vfBHvhii8wtP4LU/edit?usp=sharing +union AirtonProtocol{ + uint64_t raw; ///< The state in code form. + struct { // Common + // Byte 1 & 0 (LSB) + uint16_t Header :16; // Header. (0x11D3) + // Byte 2 + uint8_t Mode :3; // Operating Mode + uint8_t Power :1; // Power Control + uint8_t Fan :3; + uint8_t Turbo :1; + // Byte 3 + uint8_t Temp :4; // Degrees Celsius (+16 offset) + uint8_t :4; // Unknown / Unused. + // Byte 4 + uint8_t SwingV :1; + uint8_t :7; // Unknown / Unused. + // Byte 5 + uint8_t Econo :1; + uint8_t Sleep :1; + uint8_t NotAutoOn :1; + uint8_t :1; // Unknown / Unused. + uint8_t HeatOn :1; + uint8_t :1; // Unknown / Unused. + uint8_t Health :1; + uint8_t Light :1; + // Byte 6 + uint8_t Sum :8; // Sepecial checksum value + }; +}; + +// Constants +const uint8_t kAirtonAuto = 0b000; // 0 +const uint8_t kAirtonCool = 0b001; // 1 +const uint8_t kAirtonDry = 0b010; // 2 +const uint8_t kAirtonFan = 0b011; // 3 +const uint8_t kAirtonHeat = 0b100; // 4 + +const uint8_t kAirtonFanAuto = 0b000; // 0 +const uint8_t kAirtonFanMin = 0b001; // 1 +const uint8_t kAirtonFanLow = 0b010; // 2 +const uint8_t kAirtonFanMed = 0b011; // 3 +const uint8_t kAirtonFanHigh = 0b100; // 4 +const uint8_t kAirtonFanMax = 0b101; // 5 + +const uint8_t kAirtonMinTemp = 16; // 16C +const uint8_t kAirtonMaxTemp = 25; // 25C + + +/// Class for handling detailed Airton 56-bit A/C messages. +class IRAirtonAc { + public: + explicit IRAirtonAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); +#if SEND_AIRTON + void send(const uint16_t repeat = kAirtonDefaultRepeat); + /// 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_AIRTON + void begin(void); + void on(void); + void off(void); + void setPower(const bool on); + bool getPower(void) const; + void setTemp(const uint8_t degrees); + uint8_t getTemp(void) const; + void setFan(const uint8_t speed); + uint8_t getFan(void) const; + void setMode(const uint8_t mode); + uint8_t getMode(void) const; + uint64_t getRaw(void); + void setRaw(const uint64_t data); + void setLight(const bool on); + bool getLight(void) const; + void setEcono(const bool on); + bool getEcono(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; + void setHealth(const bool on); + bool getHealth(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; + static bool validChecksum(const uint64_t data); + static uint8_t calcChecksum(const uint64_t data); + static uint8_t convertMode(const stdAc::opmode_t mode); + static uint8_t convertFan(const stdAc::fanspeed_t speed); + 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); + stdAc::state_t toCommon(void) const; + String toString(void) const; +#ifndef UNIT_TEST + + private: + IRsend _irsend; ///< Instance of the IR send class +#else // UNIT_TEST + /// @cond IGNORE + IRsendTest _irsend; ///< Instance of the testing IR send class + /// @endcond +#endif // UNIT_TEST + AirtonProtocol _; + void checksum(void); +}; +#endif // IR_AIRTON_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index d43cfcd9d..1c2c94b79 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1,6 +1,7 @@ // Copyright 2019-2021 David Conran #include +#include "ir_Airton.h" #include "ir_Airwell.h" #include "ir_Amcor.h" #include "ir_Argo.h" @@ -44,6 +45,36 @@ // Tests for IRac class. +TEST(TestIRac, Airton) { + IRAirtonAc ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + const char expected[] = + "Power: On, Mode: 1 (Cool), Fan: 5 (Maximum), Temp: 18C, " + "Swing(V): On, Econo: On, Turbo: On, Light: On, Health: On, Sleep: On"; + + ac.begin(); + irac.airton(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 18, // Celsius + stdAc::fanspeed_t::kMax, // Fan speed + stdAc::swingv_t::kAuto, // Vertical Swing + true, // Turbo + true, // Light/Display/LED + true, // Econo (Eco) + true, // Filter (Health) + 9 * 60 + 12); // Sleep (09:12) + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(AIRTON, ac._irsend.capture.decode_type); + ASSERT_EQ(kAirtonBits, 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, Airwell) { IRAirwellAc ac(kGpioUnused); IRac irac(kGpioUnused); diff --git a/test/ir_Airton_test.cpp b/test/ir_Airton_test.cpp index e4e66ee13..2f2bb0bac 100644 --- a/test/ir_Airton_test.cpp +++ b/test/ir_Airton_test.cpp @@ -1,5 +1,6 @@ // Copyright 2021 crankyoldgit +#include "ir_Airton.h" #include "IRac.h" #include "IRrecv.h" #include "IRrecv_test.h" @@ -34,6 +35,13 @@ TEST(TestDecodeAirton, RealExample) { EXPECT_EQ(0x5E1400090C11D3, irsend.capture.value); EXPECT_EQ(0x0, irsend.capture.address); EXPECT_EQ(0x0, irsend.capture.command); + EXPECT_EQ( + "Power: On, Mode: 4 (Heat), Fan: 0 (Auto), Temp: 25C, " + "Swing(V): Off, Econo: Off, Turbo: Off, Light: Off, " + "Health: Off, Sleep: Off", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); } TEST(TestDecodeAirton, SyntheticExample) { @@ -56,7 +64,265 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("AIRTON", typeToString(decode_type_t::AIRTON)); ASSERT_EQ(decode_type_t::AIRTON, strToDecodeType("AIRTON")); ASSERT_FALSE(hasACState(decode_type_t::AIRTON)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::AIRTON)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::AIRTON)); ASSERT_EQ(kAirtonBits, IRsend::defaultBits(decode_type_t::AIRTON)); ASSERT_EQ(kAirtonDefaultRepeat, IRsend::minRepeats(decode_type_t::AIRTON)); } + +// Tests for IRAirtonAc class. + +TEST(TestIRAirtonAcClass, Power) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + 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(TestIRAirtonAcClass, Checksums) { + ASSERT_TRUE(IRAirtonAc::validChecksum(0x5E1400090C11D3)); + ASSERT_EQ(0x5E, IRAirtonAc::calcChecksum(0x5E1400090C11D3)); + ASSERT_FALSE(IRAirtonAc::validChecksum(0x551400090C11D3)); + ASSERT_TRUE(IRAirtonAc::validChecksum(0x2F8801060911D3)); + ASSERT_TRUE(IRAirtonAc::validChecksum(0xDB8800021A11D3)); +} + +TEST(TestIRAirtonAcClass, Temperature) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + ac.setMode(kAirtonCool); // Cool mode allows the entire temp range. + ac.setTemp(0); + EXPECT_EQ(kAirtonMinTemp, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kAirtonMaxTemp, ac.getTemp()); + + ac.setTemp(kAirtonMinTemp); + EXPECT_EQ(kAirtonMinTemp, ac.getTemp()); + + ac.setTemp(kAirtonMaxTemp); + EXPECT_EQ(kAirtonMaxTemp, ac.getTemp()); + + ac.setTemp(kAirtonMinTemp - 1); + EXPECT_EQ(kAirtonMinTemp, ac.getTemp()); + + ac.setTemp(kAirtonMaxTemp + 1); + EXPECT_EQ(kAirtonMaxTemp, ac.getTemp()); + + ac.setTemp(17); + EXPECT_EQ(17, ac.getTemp()); + + ac.setTemp(21); + EXPECT_EQ(21, ac.getTemp()); + + ac.setTemp(20); + EXPECT_EQ(20, ac.getTemp()); +} + +TEST(TestIRAirtonAcClass, OperatingMode) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setMode(kAirtonCool); + EXPECT_EQ(kAirtonCool, ac.getMode()); + ac.setMode(kAirtonDry); + EXPECT_EQ(kAirtonDry, ac.getMode()); + ac.setMode(kAirtonFan); + EXPECT_EQ(kAirtonFan, ac.getMode()); + EXPECT_NE(kAirtonMaxTemp, ac.getTemp()); + ac.setMode(kAirtonAuto); + EXPECT_EQ(kAirtonAuto, ac.getMode()); + EXPECT_EQ(kAirtonMaxTemp, ac.getTemp()); + ac.setMode(kAirtonHeat); + EXPECT_EQ(kAirtonHeat, ac.getMode()); + + ac.setMode(kAirtonHeat + 1); + EXPECT_EQ(kAirtonAuto, ac.getMode()); + ac.setMode(255); + EXPECT_EQ(kAirtonAuto, ac.getMode()); +} + +TEST(TestIRAirtonAcClass, FanSpeed) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + ac.setMode(kAirtonCool); // All fan speeds available in this mode. + + ac.setFan(0); + EXPECT_EQ(kAirtonFanAuto, ac.getFan()); + + ac.setFan(255); + EXPECT_EQ(kAirtonFanAuto, ac.getFan()); + + ac.setFan(kAirtonFanHigh); + EXPECT_EQ(kAirtonFanHigh, ac.getFan()); + + ac.setFan(kAirtonFanLow); + EXPECT_EQ(kAirtonFanLow, ac.getFan()); + + ac.setFan(kAirtonFanMax); + EXPECT_EQ(kAirtonFanMax, ac.getFan()); + + ac.setFan(kAirtonFanMin); + EXPECT_EQ(kAirtonFanMin, ac.getFan()); + + ac.setFan(kAirtonFanMed); + EXPECT_EQ(kAirtonFanMed, ac.getFan()); + + ac.setFan(kAirtonFanMax + 1); + EXPECT_EQ(kAirtonFanAuto, ac.getFan()); +} + +TEST(TestIRAirtonAcClass, SwingV) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setSwingV(false); + EXPECT_FALSE(ac.getSwingV()); + ac.setSwingV(true); + EXPECT_TRUE(ac.getSwingV()); + ac.setSwingV(false); + EXPECT_FALSE(ac.getSwingV()); + + // Known swingv on state + ac.setRaw(0xBC0401050111D3); + EXPECT_TRUE(ac.getSwingV()); +} + +TEST(TestIRAirtonAcClass, Light) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setLight(false); + EXPECT_FALSE(ac.getLight()); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_FALSE(ac.getLight()); + + // Known light on state + ac.setRaw(0x298801040911D3); + EXPECT_TRUE(ac.getLight()); +} + +TEST(TestIRAirtonAcClass, ConstructKnownExamples) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + ac.stateReset(); + ac.on(); + ac.setMode(kAirtonHeat); + ac.setFan(kAirtonFanAuto); + ac.setTemp(25); + ac.setSwingV(false); + ac.setLight(false); + ac.setTurbo(false); + ac.setSleep(false); + ac.setEcono(false); + ac.setHealth(false); + EXPECT_EQ( + "Power: On, Mode: 4 (Heat), Fan: 0 (Auto), Temp: 25C, " + "Swing(V): Off, Econo: Off, Turbo: Off, Light: Off, " + "Health: Off, Sleep: Off", + ac.toString()); + EXPECT_EQ(0x5E1400090C11D3, ac.getRaw()); +} + +TEST(TestIRAirtonAcClass, Turbo) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + EXPECT_NE(kAirtonFanMax, ac.getFan()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + EXPECT_EQ(kAirtonFanMax, ac.getFan()); + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + + // Known Turbo on state + ac.setRaw(0x92040000D911D3); + EXPECT_TRUE(ac.getTurbo()); +} + +TEST(TestIRAirtonAcClass, Sleep) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setMode(kAirtonCool); // Sleep is available in Cool mode. + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + + ac.setSleep(true); + // Sleep is available in Heat mode, but changing modes resets it. + ac.setMode(kAirtonHeat); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + + ac.setMode(kAirtonAuto); // Sleep is NOT available in Auto mode. + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_FALSE(ac.getSleep()); + + ac.setMode(kAirtonFan); // Sleep is NOT available in Fan mode. + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_FALSE(ac.getSleep()); + + // Known Sleep on state + ac.setRaw(0xA00600000911D3); + EXPECT_TRUE(ac.getSleep()); + EXPECT_NE(kAirtonAuto, ac.getMode()); + EXPECT_NE(kAirtonFan, ac.getMode()); +} + +TEST(TestIRAirtonAcClass, Health) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + + ac.setHealth(false); + EXPECT_FALSE(ac.getHealth()); + ac.setHealth(true); + EXPECT_TRUE(ac.getHealth()); + ac.setHealth(false); + EXPECT_FALSE(ac.getHealth()); + + // Known Health on state + ac.setRaw(0xE5C900000911D3); + EXPECT_TRUE(ac.getHealth()); +} + +TEST(TestIRAirtonAcClass, Econo) { + IRAirtonAc ac(kGpioUnused); + ac.begin(); + ac.setMode(kAirtonCool); // Econo is only available in Cool. + ac.setEcono(false); + EXPECT_FALSE(ac.getEcono()); + ac.setEcono(true); + EXPECT_TRUE(ac.getEcono()); + ac.setEcono(false); + EXPECT_FALSE(ac.getEcono()); + + ac.setEcono(true); + ac.setMode(kAirtonHeat); // Econo is only available in Cool, not Heat! + EXPECT_FALSE(ac.getEcono()); + ac.setEcono(true); + EXPECT_FALSE(ac.getEcono()); + + // Known Econo on state + ac.setRaw(0xE5C900000911D3); + EXPECT_TRUE(ac.getEcono()); +}