diff --git a/src/IRac.cpp b/src/IRac.cpp index a4eb8af33..978feecd5 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -233,7 +233,10 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #endif #if SEND_MIDEA case decode_type_t::MIDEA: -#endif +#endif // SEND_MIDEA +#if SEND_MIRAGE + case decode_type_t::MIRAGE: +#endif // SEND_MIRAGE #if SEND_MITSUBISHI_AC case decode_type_t::MITSUBISHI_AC: #endif @@ -1453,6 +1456,44 @@ void IRac::midea(IRMideaAC *ac, } #endif // SEND_MIDEA +#if SEND_MIRAGE +/// Send a Mirage 120-bit A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. +/// @param[in] mode The operation mode setting. +/// @param[in] degrees The temperature setting in degrees. +/// @param[in] fan The speed setting for the fan. +/// @param[in] turbo Run the device in turbo mode. +/// @param[in] light Turn on the Light/Display. +/// @param[in] sleep The time in Nr. of mins to sleep for. < 0 is ignore. +/// @note Sleep is either on or off. The time is useless. +/// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. +void IRac::mirage(IRMirageAc *ac, + // const bool on, + const stdAc::opmode_t mode, + const float degrees, + const stdAc::fanspeed_t fan, + const bool turbo, const bool light, + const int16_t sleep, const int16_t clock) { + ac->begin(); + + // ac->setPower(on); + ac->setMode(ac->convertMode(mode)); + ac->setTemp(degrees); + ac->setFan(ac->convertFan(fan)); + // No SwingV setting available + // No SwingH setting available + ac->setTurbo(turbo); + // No Quiet setting available. + ac->setLight(light); + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + ac->setSleep(sleep >= 0); + if (clock >= 0) ac->setClock(clock * 60); // Clock is in seconds. + ac->send(); +} +#endif // SEND_MIRAGE + #if SEND_MITSUBISHI_AC /// Send a Mitsubishi A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRMitsubishiAC object to use. @@ -2799,6 +2840,16 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_MIDEA +#if SEND_MIRAGE + case MIRAGE: + { + IRMirageAc ac(_pin, _inverted, _modulation); + mirage(&ac, /* send.power, */ send.mode, degC, + send.fanspeed, send.turbo, send.light, + send.sleep, send.clock); + break; + } +#endif // SEND_MIRAGE #if SEND_MITSUBISHI_AC case MITSUBISHI_AC: { @@ -3797,6 +3848,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_TRANSCOLD +#if DECODE_MIRAGE + case decode_type_t::MIRAGE: { + IRMirageAc ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_MIRAGE default: return ""; } @@ -4072,6 +4130,14 @@ namespace IRAcUtils { break; } #endif // DECODE_MIDEA +#if DECODE_MIRAGE + case decode_type_t::MIRAGE: { + IRMirageAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_MIRAGE #if DECODE_MITSUBISHI_AC case decode_type_t::MITSUBISHI_AC: { IRMitsubishiAC ac(kGpioUnused); diff --git a/src/IRac.h b/src/IRac.h index 01eb7b7b9..61f0651cd 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -26,6 +26,7 @@ #include "ir_Kelvinator.h" #include "ir_LG.h" #include "ir_Midea.h" +#include "ir_Mirage.h" #include "ir_Mitsubishi.h" #include "ir_MitsubishiHeavy.h" #include "ir_Neoclima.h" @@ -326,6 +327,14 @@ void electra(IRElectraAc *ac, const stdAc::swingv_t swingv, const bool turbo, const bool econo, const bool light, const int16_t sleep = -1); #endif // SEND_MIDEA +#if SEND_MIRAGE + void mirage(IRMirageAc *ac, + // const bool on, + const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const bool turbo, const bool light, + const int16_t sleep = -1, const int16_t clock = -1); +#endif // SEND_MIRAGE #if SEND_MITSUBISHI_AC void mitsubishi(IRMitsubishiAC *ac, const bool on, const stdAc::opmode_t mode, diff --git a/src/ir_Mirage.cpp b/src/ir_Mirage.cpp index 8a573a8dc..72a0ea8f4 100644 --- a/src/ir_Mirage.cpp +++ b/src/ir_Mirage.cpp @@ -1,15 +1,33 @@ -// Copyright 2020 David Conran (crankyoldgit) +// Copyright 2020-2021 David Conran (crankyoldgit) /// @file /// @brief Support for Mirage protocol /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573 -// Supports: -// Brand: Mirage, Model: VLU series A/C +#include "ir_Mirage.h" +#include +#include +#ifndef ARDUINO +#include +#endif #include "IRrecv.h" #include "IRsend.h" +#include "IRtext.h" #include "IRutils.h" +using irutils::addBoolToString; +using irutils::addFanToString; +using irutils::addIntToString; +using irutils::addLabeledString; +using irutils::addModeToString; +using irutils::addSwingHToString; +using irutils::addSwingVToString; +using irutils::addTempToString; +using irutils::minsToString; +using irutils::bcdToUint8; +using irutils::uint8ToBcd; +using irutils::sumNibbles; // Constants const uint16_t kMirageHdrMark = 8360; ///< uSeconds @@ -67,4 +85,271 @@ bool IRrecv::decodeMirage(decode_results *results, uint16_t offset, // is a union data type. return true; } + +// Code to emulate Mirage A/C IR remote control unit. + +/// 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? +IRMirageAc::IRMirageAc(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { stateReset(); } + +/// Reset the state of the remote to a known good state/sequence. +void IRMirageAc::stateReset(void) { + // The state of the IR remote in IR code form. + static const uint8_t kReset[kMirageStateLength] = { + 0x56, 0x6C, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, + 0x0C, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x42}; + setRaw(kReset); +} + +/// Set up hardware to be able to send a message. +void IRMirageAc::begin(void) { _irsend.begin(); } + +#if SEND_MITSUBISHI_AC +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRMirageAc::send(const uint16_t repeat) { + _irsend.sendMirage(getRaw(), kMirageStateLength, repeat); +} +#endif // SEND_MITSUBISHI_AC + +/// Get a PTR to the internal state/code for this protocol. +/// @return PTR to a code for this protocol based on the current internal state. +uint8_t *IRMirageAc::getRaw(void) { + checksum(); + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] data A valid code for this protocol. +void IRMirageAc::setRaw(const uint8_t *data) { + std::memcpy(_.raw, data, kMirageStateLength); +} + +/// Calculate and set the checksum values for the internal state. +void IRMirageAc::checksum(void) { _.Sum = calculateChecksum(_.raw); } + +/// Verify the checksum is valid for a given state. +/// @param[in] data The array to verify the checksum of. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRMirageAc::validChecksum(const uint8_t *data) { + return calculateChecksum(data) == data[kMirageStateLength - 1]; +} + +/// Calculate the checksum for a given state. +/// @param[in] data The value to calc the checksum of. +/// @return The calculated checksum value. +uint8_t IRMirageAc::calculateChecksum(const uint8_t *data) { + return sumNibbles(data, kMirageStateLength - 1); +} + +/* DISABLED until we get power control. +/// Set the requested power state of the A/C to on. +void IRMirageAc::on(void) { setPower(true); } + +/// Set the requested power state of the A/C to off. +void IRMirageAc::off(void) { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setPower(bool on) { + _.Power = on; +} + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getPower(void) const { + return _.Power; +} +*/ + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRMirageAc::getMode(void) const { return _.Mode; } + +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +void IRMirageAc::setMode(const uint8_t mode) { + switch (mode) { + case kMirageAcCool: + case kMirageAcDry: + case kMirageAcHeat: + case kMirageAcFan: + case kMirageAcRecycle: + _.Mode = mode; + // Reset turbo if we have to. + setTurbo(getTurbo()); + break; + default: // Default to cool mode for anything else. + setMode(kMirageAcCool); + } +} + +/// Set the temperature. +/// @param[in] degrees The temperature in degrees celsius. +void IRMirageAc::setTemp(const uint8_t degrees) { + // Make sure we have desired temp in the correct range. + uint8_t celsius = std::max(degrees, kMirageAcMinTemp); + _.Temp = std::min(celsius, kMirageAcMaxTemp) + kMirageAcTempOffset; +} + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRMirageAc::getTemp(void) const { return _.Temp - kMirageAcTempOffset; } + +/// Set the speed of the fan. +/// @param[in] speed The desired setting. +void IRMirageAc::setFan(const uint8_t speed) { + _.Fan = (speed <= kMirageAcFanLow) ? speed : kMirageAcFanAuto; +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRMirageAc::getFan(void) const { return _.Fan; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setTurbo(bool on) { + _.Turbo = (on && (getMode() == kMirageAcCool)); +} + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getTurbo(void) const { return _.Turbo; } + +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setSleep(bool on) { _.Sleep = on; } + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getSleep(void) const { return _.Sleep; } + +/// Change the Light/Display setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRMirageAc::setLight(bool on) { _.Light = on; } + +/// Get the value of the current Light/Display setting. +/// @return true, the setting is on. false, the setting is off. +bool IRMirageAc::getLight(void) const { return _.Light; } + +/// Get the clock time of the A/C unit. +/// @return Nr. of seconds past midnight. +uint32_t IRMirageAc::getClock(void) const { + return ((bcdToUint8(_.Hours) * 60) + bcdToUint8(_.Minutes)) * 60 + + bcdToUint8(_.Seconds); +} + +/// Set the clock time on the A/C unit. +/// @param[in] nr_of_seconds Nr. of seconds past midnight. +void IRMirageAc::setClock(const uint32_t nr_of_seconds) { + uint32_t remaining = std::min( + nr_of_seconds, (uint32_t)(24 * 60 * 60 - 1)); // Limit to 23:59:59. + _.Seconds = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Minutes = uint8ToBcd(remaining % 60); + remaining /= 60; + _.Hours = uint8ToBcd(remaining); +} + +/// 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 IRMirageAc::toCommonMode(const uint8_t mode) { + switch (mode) { + case kMirageAcHeat: return stdAc::opmode_t::kHeat; + case kMirageAcDry: return stdAc::opmode_t::kDry; + case kMirageAcFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + +/// 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 IRMirageAc::toCommonFanSpeed(const uint8_t speed) { + switch (speed) { + case kMirageAcFanHigh: return stdAc::fanspeed_t::kHigh; + case kMirageAcFanMed: return stdAc::fanspeed_t::kMedium; + case kMirageAcFanLow: return stdAc::fanspeed_t::kLow; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// 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 IRMirageAc::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kMirageAcHeat; + case stdAc::opmode_t::kDry: return kMirageAcDry; + case stdAc::opmode_t::kFan: return kMirageAcFan; + default: return kMirageAcCool; + } +} + +/// 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 IRMirageAc::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kMirageAcFanLow; + case stdAc::fanspeed_t::kMedium: return kMirageAcFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kMirageAcFanHigh; + default: return kMirageAcFanAuto; + } +} + +/// Convert the current internal state into its stdAc::state_t equivalent. +/// @return The stdAc equivalent of the native settings. +stdAc::state_t IRMirageAc::toCommon(void) const { + stdAc::state_t result; + result.protocol = decode_type_t::MIRAGE; + result.model = -1; // No models used. + result.power = true; + result.mode = toCommonMode(_.Mode); + result.celsius = true; + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + result.turbo = getTurbo(); + result.light = getLight(); + result.sleep = getSleep() ? 0 : -1; + // Not supported. + result.swingv = stdAc::swingv_t::kOff; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.clean = false; + result.econo = false; + result.filter = false; + result.beep = false; + result.clock = -1; + return result; +} + +/// Convert the internal state into a human readable string. +/// @return A string containing the settings in human-readable form. +String IRMirageAc::toString(void) const { + String result = ""; + result.reserve(110); // Reserve some heap for the string to reduce fragging. + // result += addBoolToString(_.Power, kPowerStr, false); + result += addModeToString(_.Mode, 0xFF, kMirageAcCool, + kMirageAcHeat, kMirageAcDry, + kMirageAcFan); + result += addTempToString(getTemp()); + result += addFanToString(_.Fan, kMirageAcFanHigh, + kMirageAcFanLow, + kMirageAcFanAuto, kMirageAcFanAuto, + kMirageAcFanMed); + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(minsToString(getClock() / 60), kClockStr); + return result; +} + #endif // DECODE_MIRAGE diff --git a/src/ir_Mirage.h b/src/ir_Mirage.h new file mode 100644 index 000000000..c4e10086f --- /dev/null +++ b/src/ir_Mirage.h @@ -0,0 +1,150 @@ +// Copyright 2020-2021 David Conran (crankyoldgit) +/// @file +/// @brief Support for Mirage protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1289 +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1573 + + +// Supports: +// Brand: Mirage, Model: VLU series A/C +// Brand: Maxell, Model: MX-CH18CF A/C +// Brand: Maxell, Model: KKG9A-C1 remote + +#ifndef IR_MIRAGE_H_ +#define IR_MIRAGE_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 Mirage 120-bit A/C message. +/// @see https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0 +union Mirage120Protocol{ + uint8_t raw[kMirageStateLength]; ///< The state in code form. + struct { + // Byte 0 + uint8_t :8; // Header. (0x56) + // Byte 1 + uint8_t Temp :8; // Celsius minus 0x5C. + // Byte 2 + uint8_t :8; // Unknown / Unused. Typically 0x00 + // Byte 3 + uint8_t :3; // Unknown / Unused. Typically 0x0 + uint8_t Light :1; // Aka. Display. Seems linked to Sleep mode. + uint8_t :4; // Unknown / Unused. Typically 0x0 + // Byte 4 + uint8_t Fan :2; // Fan Speed. + uint8_t :2; // Unknown / Unused. Typically 0x0 + uint8_t Mode :4; // Cool, Heat, Dry, Fan, Recycle + // Byte 5 + uint8_t Swing :8; + // Byte 6 + uint8_t :7; // Unknown / Unused. Typically 0x00 + uint8_t Sleep :1; // Sleep mode on or off. + // Byte 7 + uint8_t :3; // Unknown / Unused. Typically 0x0 + uint8_t Turbo :1; // Sleep mode on or off. Only works in Cool mode. + uint8_t :4; // Unknown / Unused. Typically 0x0 + // Byte 8 + uint8_t :8; // Unknown / Unused. Typically 0xC0 + // Byte 9 + uint8_t :8; // Unknown / Unused. Typically 0x00 + // Byte 10 + uint8_t :8; // Unknown / Unused. + // Byte 11 + uint8_t Seconds :8; // Nr. of Seconds in BCD. + // Byte 12 + uint8_t Minutes :8; // Nr. of Minutes in BCD. + // Byte 13 + uint8_t Hours :8; // Nr. of Hours in BCD. + // Byte 14 + uint8_t Sum :8; // Sum of all the previous nibbles. + }; +}; + +// Constants +const uint8_t kMirageAcHeat = 0b001; // 1 +const uint8_t kMirageAcCool = 0b010; // 2 +const uint8_t kMirageAcDry = 0b011; // 3 +const uint8_t kMirageAcRecycle = 0b100; // 4 +const uint8_t kMirageAcFan = 0b101; // 5 + +const uint8_t kMirageAcFanAuto = 0b00; // 0 +const uint8_t kMirageAcFanHigh = 0b01; // 1 +const uint8_t kMirageAcFanMed = 0b10; // 2 +const uint8_t kMirageAcFanLow = 0b11; // 3 + +const uint8_t kMirageAcMinTemp = 16; // 16C +const uint8_t kMirageAcMaxTemp = 32; // 32C +const uint8_t kMirageAcTempOffset = 0x5C; + +/// Class for handling detailed Mirage 120-bit A/C messages. +/// @note Inspired and derived from the work done at: https://github.com/r45635/HVAC-IR-Control +/// @warning Consider this very alpha code. Seems to work, but not validated. +class IRMirageAc { + public: + explicit IRMirageAc(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(void); + static bool validChecksum(const uint8_t* data); +#if SEND_MIRAGE + void send(const uint16_t repeat = kMirageMinRepeat); + /// 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_MIRAGE + 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; + uint8_t* getRaw(void); + void setRaw(const uint8_t* data); + uint32_t getClock(void) const; + void setClock(const uint32_t nr_of_seconds); + void setTurbo(const bool on); + bool getTurbo(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setSleep(const bool on); + bool getSleep(void) const; + + 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 uint8_t convertSwingH(const stdAc::swingh_t position); + static stdAc::opmode_t toCommonMode(const uint8_t mode); + static stdAc::fanspeed_t toCommonFanSpeed(const uint8_t speed); + static stdAc::swingv_t toCommonSwingV(const uint8_t pos); + static stdAc::swingh_t toCommonSwingH(const uint8_t pos); + 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 + Mirage120Protocol _; + void checksum(void); + static uint8_t calculateChecksum(const uint8_t* data); +}; +#endif // IR_MIRAGE_H_ diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index de8f9ea7a..76ed7de14 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -19,6 +19,7 @@ #include "ir_Kelvinator.h" #include "ir_LG.h" #include "ir_Midea.h" +#include "ir_Mirage.h" #include "ir_Mitsubishi.h" #include "ir_MitsubishiHeavy.h" #include "ir_Neoclima.h" @@ -1252,6 +1253,35 @@ TEST(TestIRac, Midea) { ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); } +TEST(TestIRac, Mirage) { + IRMirageAc ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + char expected[] = + ", Mode: 3 (Dry), Temp: 27C, Fan: 2 (Medium), " + "Turbo: Off, Light: Off, Sleep: On, Clock: 17:31"; + + ac.begin(); + irac.mirage(&ac, + // true, // Power + stdAc::opmode_t::kDry, // Mode + 27, // Degrees (Celsius) + stdAc::fanspeed_t::kMedium, // Fan speed + false, // Turbo + false, // Light + 8 * 60 + 0, // Sleep time + 17 * 60 + 31); // Clock + + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(MIRAGE, ac._irsend.capture.decode_type); + ASSERT_EQ(kMirageBits, ac._irsend.capture.bits); + ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); +} + TEST(TestIRac, Mitsubishi) { IRMitsubishiAC ac(kGpioUnused); IRac irac(kGpioUnused); diff --git a/test/ir_Mirage_test.cpp b/test/ir_Mirage_test.cpp index 2530afc5c..6d500bc87 100644 --- a/test/ir_Mirage_test.cpp +++ b/test/ir_Mirage_test.cpp @@ -1,5 +1,6 @@ -// Copyright 2020 David Conran +// Copyright 2020-2021 David Conran +#include "ir_Mirage.h" #include "IRac.h" #include "IRrecv.h" #include "IRrecv_test.h" @@ -11,7 +12,7 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("MIRAGE", typeToString(decode_type_t::MIRAGE)); ASSERT_EQ(decode_type_t::MIRAGE, strToDecodeType("MIRAGE")); ASSERT_TRUE(hasACState(decode_type_t::MIRAGE)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::MIRAGE)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::MIRAGE)); ASSERT_EQ(kMirageBits, IRsend::defaultBits(decode_type_t::MIRAGE)); ASSERT_EQ(kMirageMinRepeat, IRsend::minRepeats(decode_type_t::MIRAGE)); } @@ -55,7 +56,8 @@ TEST(TestDecodeMirage, RealExample) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "", + ", Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -76,7 +78,8 @@ TEST(TestDecodeMirage, SyntheticExample) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "", + ", Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } @@ -119,6 +122,155 @@ TEST(TestDecodeMirage, RealExampleWithDodgyHardwareCapture) { ASSERT_EQ(kMirageBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "", + ", Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", IRAcUtils::resultAcToString(&irsend.capture)); } + +TEST(TestMirageAcClass, OperatingMode) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setMode(kMirageAcCool); + EXPECT_EQ(kMirageAcCool, ac.getMode()); + ac.setMode(kMirageAcHeat); + EXPECT_EQ(kMirageAcHeat, ac.getMode()); + ac.setMode(kMirageAcDry); + EXPECT_EQ(kMirageAcDry, ac.getMode()); + ac.setMode(kMirageAcFan); + EXPECT_EQ(kMirageAcFan, ac.getMode()); + ac.setMode(kMirageAcRecycle); + EXPECT_EQ(kMirageAcRecycle, ac.getMode()); + ac.setMode(255); + EXPECT_EQ(kMirageAcCool, ac.getMode()); +} + +TEST(TestMirageAcClass, HumanReadable) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + EXPECT_EQ( + ", Mode: 2 (Cool), Temp: 16C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 00:00", + ac.toString()); + // Ref: https://docs.google.com/spreadsheets/d/1Ucu9mOOIIJoWQjUJq_VCvwgV3EwKaRk8K2AuZgccYEk/edit#gid=0&range=C7 + // 0x56710000201A00000C000C26010041 + const uint8_t cool_21c_auto[kMirageStateLength] = { + 0x56, 0x71, 0x00, 0x00, 0x20, 0x1A, 0x00, 0x00, + 0x0C, 0x00, 0x0C, 0x26, 0x01, 0x00, 0x41}; + ac.setRaw(cool_21c_auto); + EXPECT_EQ( + ", Mode: 2 (Cool), Temp: 21C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 00:01", + ac.toString()); + + const uint8_t SyntheticExample[kMirageStateLength] = { + 0x56, 0x75, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x16, 0x14, 0x26}; + ac.setRaw(SyntheticExample); + EXPECT_EQ( + ", Mode: 2 (Cool), Temp: 25C, Fan: 0 (Auto), " + "Turbo: Off, Light: Off, Sleep: Off, Clock: 14:16", + ac.toString()); +} + +TEST(TestMirageAcClass, Temperature) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setTemp(0); + EXPECT_EQ(kMirageAcMinTemp, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kMirageAcMaxTemp, ac.getTemp()); + + ac.setTemp(kMirageAcMinTemp); + EXPECT_EQ(kMirageAcMinTemp, ac.getTemp()); + + ac.setTemp(kMirageAcMaxTemp); + EXPECT_EQ(kMirageAcMaxTemp, ac.getTemp()); + + ac.setTemp(kMirageAcMinTemp - 1); + EXPECT_EQ(kMirageAcMinTemp, ac.getTemp()); + + ac.setTemp(kMirageAcMaxTemp + 1); + EXPECT_EQ(kMirageAcMaxTemp, 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(30); + EXPECT_EQ(30, ac.getTemp()); +} + +TEST(TestMirageAcClass, FanSpeed) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setFan(kMirageAcFanAuto); + EXPECT_EQ(kMirageAcFanAuto, ac.getFan()); + ac.setFan(kMirageAcFanLow); + EXPECT_EQ(kMirageAcFanLow, ac.getFan()); + ac.setFan(kMirageAcFanMed); + EXPECT_EQ(kMirageAcFanMed, ac.getFan()); + ac.setFan(kMirageAcFanHigh); + EXPECT_EQ(kMirageAcFanHigh, ac.getFan()); + + ac.setFan(255); + EXPECT_EQ(kMirageAcFanAuto, ac.getFan()); +} + +TEST(TestMirageAcClass, Turbo) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); +} + +TEST(TestMirageAcClass, Light) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_FALSE(ac.getLight()); + ac.setLight(true); + EXPECT_TRUE(ac.getLight()); +} + +TEST(TestMirageAcClass, Sleep) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); +} + +TEST(TestMirageAcClass, Clock) { + IRMirageAc ac(kGpioUnused); + ac.begin(); + + ac.setClock(0); + EXPECT_EQ(0, ac.getClock()); + ac.setClock(12 * 60 * 60 + 30 * 60 + 59); // aka. 12:30:59 + EXPECT_EQ(12 * 60 * 60 + 30 * 60 + 59, ac.getClock()); + ac.setClock(23 * 60 * 60 + 59 * 60 + 59); // aka. 23:59:59 + EXPECT_EQ(23 * 60 * 60 + 59 * 60 + 59, ac.getClock()); + ac.setClock(24 * 60 * 60); // aka. 24:00:00 + EXPECT_EQ(23 * 60 * 60 + 59 * 60 + 59, ac.getClock()); // aka. 23:59:59 +}