From 2ce02032e4e54040fe97458d8fa86e502fad0a5b Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Aug 2020 00:13:33 +1000 Subject: [PATCH 01/16] Voltas: Progress check-in * Checksum support. * Power, toString, toCommon, set/GetRaw etc. For #1238 --- src/IRac.cpp | 16 +++++ src/ir_Voltas.cpp | 143 +++++++++++++++++++++++++++++++++++++++- src/ir_Voltas.h | 128 +++++++++++++++++++++++++++++++++++ test/ir_Voltas_test.cpp | 44 +++++++++++++ 4 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 src/ir_Voltas.h diff --git a/src/IRac.cpp b/src/IRac.cpp index 4e54d26f0..8980433b5 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -42,6 +42,7 @@ #include "ir_Toshiba.h" #include "ir_Trotec.h" #include "ir_Vestel.h" +#include "ir_Voltas.h" #include "ir_Whirlpool.h" /// Class constructor @@ -3112,6 +3113,13 @@ namespace IRAcUtils { return ac.toString(); } #endif // DECODE_VESTEL_AC +#if DECODE_VOLTAS + case decode_type_t::VOLTAS: { + IRVoltas ac(kGpioUnused); + ac.setRaw(result->state); + return ac.toString(); + } +#endif // DECODE_VOLTAS #if DECODE_TECO case decode_type_t::TECO: { IRTecoAc ac(kGpioUnused); @@ -3513,6 +3521,14 @@ namespace IRAcUtils { break; } #endif // DECODE_VESTEL_AC +#if DECODE_VOLTAS + case decode_type_t::VOLTAS: { + IRVestelAc ac(kGpioUnused); + ac.setRaw(decode->state); + *result = ac.toCommon(); + break; + } +#endif // DECODE_VOLTAS #if DECODE_WHIRLPOOL_AC case decode_type_t::WHIRLPOOL_AC: { IRWhirlpoolAc ac(kGpioUnused); diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index f0fe910f7..8a1051481 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -4,13 +4,19 @@ /// @brief Support for Voltas A/C protocol /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 -// Supports: -// Brand: Voltas, Model: 122LZF 4011252 Window A/C - +#include "ir_Voltas.h" +#include #include "IRrecv.h" #include "IRsend.h" +#include "IRtext.h" #include "IRutils.h" +using irutils::addBoolToString; +using irutils::addModeToString; +using irutils::addFanToString; +using irutils::addTempToString; +using irutils::minsToString; + // Constants const uint16_t kVoltasBitMark = 1026; ///< uSeconds. const uint16_t kVoltasOneSpace = 2553; ///< uSeconds. @@ -61,9 +67,140 @@ bool IRrecv::decodeVoltas(decode_results *results, uint16_t offset, kVoltasBitMark, kVoltasZeroSpace, kVoltasBitMark, kDefaultMessageGap, true)) return false; + // Compliance + if (strict && !IRVoltas::validChecksum(results->state, nbits / 8)) + return false; // Success results->decode_type = decode_type_t::VOLTAS; results->bits = nbits; return true; } #endif // DECODE_VOLTAS + +/// 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? +IRVoltas::IRVoltas(const uint16_t pin, const bool inverted, + const bool use_modulation) + : _irsend(pin, inverted, use_modulation) { + stateReset(); +} + +// Reset the internal state to a fixed known good state. +void IRVoltas::stateReset() { + // This resets to a known-good state. + std::memset(_.raw, 0, sizeof _.raw); +} + +/// Set up hardware to be able to send a message. +void IRVoltas::begin() { _irsend.begin(); } + +#if SEND_VOLTAS +/// Send the current internal state as an IR message. +/// @param[in] repeat Nr. of times the message will be repeated. +void IRVoltas::send(const uint16_t repeat) { + _irsend.sendVoltas(getRaw(), kVoltasStateLength, repeat); +} +#endif // SEND_VOLTAS + +/// 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* IRVoltas::getRaw(void) { + checksum(); // Ensure correct settings before sending. + return _.raw; +} + +/// Set the internal state from a valid code for this protocol. +/// @param[in] new_code A valid code for this protocol. +void IRVoltas::setRaw(const uint8_t new_code[]) { + std::memcpy(_.raw, new_code, kVoltasStateLength); +} + +/// Calculate and set the checksum values for the internal state. +void IRVoltas::checksum(void) { + _.Checksum = calcChecksum(_.raw, kVoltasStateLength - 1); +} + +/// Verify the checksum is valid for a given state. +/// @param[in] state The array to verify the checksum of. +/// @param[in] length The length of the state array. +/// @return true, if the state has a valid checksum. Otherwise, false. +bool IRVoltas::validChecksum(const uint8_t state[], const uint16_t length) { + if (length) return state[length - 1] == calcChecksum(state, length); + return true; +} + +/// Calculate the checksum is valid for a given state. +/// @param[in] state The array to calculate the checksum of. +/// @param[in] length The length of the state array. +/// @return The valid checksum value for the state. +uint8_t IRVoltas::calcChecksum(const uint8_t state[], const uint16_t length) { + uint8_t result = 0; + if (length) + result = sumBytes(state, length - 1); + return ~result; +} + +/// Change the power setting to On. +void IRVoltas::on() { setPower(true); } + +/// Change the power setting to Off. +void IRVoltas::off() { setPower(false); } + +/// Change the power setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setPower(const bool on) { _.Power = on; } + +/// Get the value of the current power setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getPower(void) const { return _.Power; } + +/// Convert the current internal state into its stdAc::state_t equivilant. +/// @return The stdAc equivilant of the native settings. +stdAc::state_t IRVoltas::toCommon() { + stdAc::state_t result; + result.protocol = decode_type_t::VOLTAS; + result.power = _.Power; + // result.mode = toCommonMode(getMode()); + result.celsius = true; + /* + result.degrees = getTemp(); + result.fanspeed = toCommonFanSpeed(getFan()); + if (getSwingVerticalAuto()) + result.swingv = stdAc::swingv_t::kAuto; + else + result.swingv = toCommonSwingV(getSwingVerticalPosition()); + */ + result.turbo = _.Turbo; + result.econo = _.Econo; + result.light = _.Light; + /* + result.clean = getXFan(); + result.sleep = getSleep() ? 0 : -1; + */ + // Not supported. + result.model = -1; + result.swingh = stdAc::swingh_t::kOff; + result.quiet = false; + result.filter = 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 IRVoltas::toString() { + String result = ""; + result.reserve(80); // Reserve some heap for the string to reduce fragging. + result += addBoolToString(_.Power, kPowerStr, false); + /* + result += addModeToString(getMode(), kVoltasAuto, kVoltasCool, kVoltasHeat, + kVoltasDry, kVoltasFan); + */ + result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Wifi, kWifiStr); + result += addBoolToString(_.Light, kLightStr); + return result; +} diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h new file mode 100644 index 000000000..a069e2551 --- /dev/null +++ b/src/ir_Voltas.h @@ -0,0 +1,128 @@ +// Copyright 2020 David Conran (crankyoldgit) +// Copyright 2020 manj9501 +/// @file +/// @brief Support for Voltas A/C protocol +/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 + +// Supports: +// Brand: Voltas, Model: 122LZF 4011252 Window A/C +// +// Ref: https://docs.google.com/spreadsheets/d/1zzDEUQ52y7MZ7_xCU3pdjdqbRXOwZLsbTGvKWcicqCI/ +// Ref: https://www.corona.co.jp/box/download.php?id=145060636229 + +#ifndef IR_VOLTAS_H_ +#define IR_VOLTAS_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 + + +union VoltasProtocol { + uint8_t raw[kVoltasStateLength]; ///< The state in native IR code form + struct { + // Byte 0 + uint8_t SwingH :1; + uint8_t Unknown0 :7; + // Byte 1 + uint8_t Mode :4; + uint8_t :1; + uint8_t FanSpeed :3; + // Byte 2 + uint8_t SwingV :3; + uint8_t Wifi :1; + uint8_t :1; + uint8_t Turbo :1; + uint8_t Sleep :1; + uint8_t Power :1; + // Byte 3 + uint8_t Temp :4; + uint8_t Unknown3 :2; // Typically 0b01 + uint8_t Econo :1; + uint8_t TempSet :1; + // Byte 4 + uint8_t OffTimer24h4 :1; + uint8_t :7; // Typically 0b0011101 + // Byte 5 + uint8_t OffTimer24h5 :1; + uint8_t :6; // Typically 0b011101 + uint8_t TimerAdd12Hr :1; + // Byte 6 + uint8_t :8; // Typically 0b00111011(0x3B) + // Byte 7 + uint8_t :4; // Typically 0b0001 + uint8_t TimerHrs :4; // Nr of Hours. + // Byte 8 + uint8_t :5; // Typically 0b00000 + uint8_t Light :1; + uint8_t OffTimerEnable :1; + uint8_t :1; // Typically 0b0 + // Byte 9 + uint8_t Checksum :8; + }; +}; + +// Constants + +// Classes +/// Class for handling detailed Voltas A/C messages. +class IRVoltas { + public: + explicit IRVoltas(const uint16_t pin, const bool inverted = false, + const bool use_modulation = true); + void stateReset(); +#if SEND_VOLTAS + void send(const uint16_t repeat = kNoRepeat); + /// 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_VOLTAS + void begin(); + static bool validChecksum(const uint8_t state[], + const uint16_t length = kVoltasStateLength); + void setPower(const bool on); + bool getPower(void) const; + void on(void); + void off(void); + void setTemp(const uint8_t temp); + uint8_t getTemp(void); + void setFan(const uint8_t speed); + uint8_t getFan(void); + void setMode(const uint8_t mode); + uint8_t getMode(void); + void setEcono(const bool on); + bool getEcono(void); + void setOffTimer(const uint16_t nr_of_mins); + uint16_t getOffTimer(void); + uint8_t* getRaw(void); + void setRaw(const uint8_t new_code[]); + 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(void); +#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 + VoltasProtocol _; ///< The state of the IR remote. + void checksum(void); + static uint8_t calcChecksum(const uint8_t state[], + const uint16_t length = kVoltasStateLength); +}; +#endif // IR_VOLTAS_H_ diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 8931fcab3..bbec7e26c 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -1,5 +1,6 @@ // Copyright 2020 crankyoldgit +#include "ir_Voltas.h" #include "IRac.h" #include "IRrecv.h" #include "IRrecv_test.h" @@ -38,6 +39,11 @@ TEST(TestDecodeVoltas, RealExample) { ASSERT_EQ(decode_type_t::VOLTAS, irsend.capture.decode_type); ASSERT_EQ(kVoltasBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); + EXPECT_EQ( + "Power: On, Turbo: Off, WiFi: On, Light: Off", + IRAcUtils::resultAcToString(&irsend.capture)); + stdAc::state_t r, p; + ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); } TEST(TestDecodeVoltas, SyntheticExample) { @@ -65,3 +71,41 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ(kVoltasBits, IRsend::defaultBits(decode_type_t::VOLTAS)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::VOLTAS)); } + +TEST(TestIRVoltasClass, Checksums) { + const uint8_t valid[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + EXPECT_TRUE(IRVoltas::validChecksum(valid)); + EXPECT_FALSE(IRVoltas::validChecksum(valid, kVoltasStateLength - 1)); + EXPECT_EQ(0xE6, IRVoltas::calcChecksum(valid)); +} + +TEST(TestIRVoltasClass, SetandGetRaw) { + const uint8_t valid[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + const uint8_t badchecksum[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + IRVoltas ac(kGpioUnused); + + ac.setRaw(valid); + EXPECT_STATE_EQ(valid, ac.getRaw(), kVoltasBits); + ac.setRaw(badchecksum); + EXPECT_STATE_EQ(valid, ac.getRaw(), kVoltasBits); +} + +TEST(TestIRVoltasClass, Power) { + IRVoltas 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()); +} From 67184cd2f0d9ddd5a34b93f2b5d0a182787da639 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Aug 2020 15:07:08 +1000 Subject: [PATCH 02/16] IRVoltas: Add more detailed support. * Operating Modes * Temperature * Fan Speed * Wifi * Turbo * Econo * Light * More `IRac` support functions. * Unit test coverage for the above. Note: Does not include fixed settings when changing to certain modes (yet). For #1238 --- src/ir_Voltas.cpp | 125 ++++++++++++++++++++++++++++++++++++++-- src/ir_Voltas.h | 18 +++++- test/ir_Voltas_test.cpp | 125 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 260 insertions(+), 8 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 8a1051481..a22764015 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -5,6 +5,7 @@ /// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1238 #include "ir_Voltas.h" +#include #include #include "IRrecv.h" #include "IRsend.h" @@ -156,6 +157,116 @@ void IRVoltas::setPower(const bool on) { _.Power = on; } /// @return true, the setting is on. false, the setting is off. bool IRVoltas::getPower(void) const { return _.Power; } +/// Set the operating mode of the A/C. +/// @param[in] mode The desired operating mode. +/// @note If we get an unexpected mode, default to AUTO. +void IRVoltas::setMode(const uint8_t mode) { + switch (mode) { + case kVoltasFan: + case kVoltasHeat: + case kVoltasDry: + case kVoltasCool: + break; + default: + setMode(kVoltasCool); + return; + } + _.Mode = mode; +} + +/// Get the operating mode setting of the A/C. +/// @return The current operating mode setting. +uint8_t IRVoltas::getMode(void) { return _.Mode; } + +/// Set the temperature. +/// @param[in] temp The temperature in degrees celsius. +void IRVoltas::setTemp(const uint8_t temp) { + uint8_t new_temp = std::max(kVoltasMinTemp, temp); + new_temp = std::min(kVoltasMaxTemp, new_temp); + _.Temp = new_temp - kVoltasMinTemp; +} + +/// Set the speed of the fan. +/// @param[in] fan The desired setting. +void IRVoltas::setFan(const uint8_t fan) { + switch (fan) { + case kVoltasFanLow: + case kVoltasFanMed: + case kVoltasFanHigh: + case kVoltasFanAuto: + _.FanSpeed = fan; + break; + default: + setFan(kVoltasFanAuto); + } +} + +/// 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 IRVoltas::convertFan(const stdAc::fanspeed_t speed) { + switch (speed) { + case stdAc::fanspeed_t::kMin: + case stdAc::fanspeed_t::kLow: return kVoltasFanLow; + case stdAc::fanspeed_t::kMedium: return kVoltasFanMed; + case stdAc::fanspeed_t::kHigh: + case stdAc::fanspeed_t::kMax: return kVoltasFanHigh; + default: return kVoltasFanAuto; + } +} + +/// Convert a native fan speed into its stdAc equivilant. +/// @param[in] spd The native setting to be converted. +/// @return The stdAc equivilant of the native setting. +stdAc::fanspeed_t IRVoltas::toCommonFanSpeed(const uint8_t spd) { + switch (spd) { + case kVoltasFanHigh: return stdAc::fanspeed_t::kMax; + case kVoltasFanMed: return stdAc::fanspeed_t::kMedium; + case kVoltasFanLow: return stdAc::fanspeed_t::kMin; + default: return stdAc::fanspeed_t::kAuto; + } +} + +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRVoltas::getFan(void) { return _.FanSpeed; } + +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRVoltas::getTemp(void) { return _.Temp + kVoltasMinTemp; } + +/// Change the Wifi setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setWifi(const bool on) { _.Wifi = on; } + +/// Get the value of the current Wifi setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getWifi(void) const { return _.Wifi; } + +/// Change the Turbo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setTurbo(const bool on) { _.Turbo = on; } + +/// Get the value of the current Turbo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getTurbo(void) const { return _.Turbo; } + +/// Change the Econo setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setEcono(const bool on) { _.Econo = on; } + +/// Get the value of the current Econo setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getEcono(void) const { return _.Econo; } + +/// Change the Light setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setLight(const bool on) { _.Light = on; } + +/// Get the value of the current Light setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getLight(void) const { return _.Light; } + /// Convert the current internal state into its stdAc::state_t equivilant. /// @return The stdAc equivilant of the native settings. stdAc::state_t IRVoltas::toCommon() { @@ -164,9 +275,9 @@ stdAc::state_t IRVoltas::toCommon() { result.power = _.Power; // result.mode = toCommonMode(getMode()); result.celsius = true; - /* result.degrees = getTemp(); - result.fanspeed = toCommonFanSpeed(getFan()); + result.fanspeed = toCommonFanSpeed(_.FanSpeed); + /* if (getSwingVerticalAuto()) result.swingv = stdAc::swingv_t::kAuto; else @@ -193,13 +304,15 @@ stdAc::state_t IRVoltas::toCommon() { /// @return A human readable string. String IRVoltas::toString() { String result = ""; - result.reserve(80); // Reserve some heap for the string to reduce fragging. + result.reserve(100); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); - /* - result += addModeToString(getMode(), kVoltasAuto, kVoltasCool, kVoltasHeat, + result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, kVoltasDry, kVoltasFan); - */ + result += addTempToString(getTemp()); + result += addFanToString(_.FanSpeed, kVoltasFanHigh, kVoltasFanLow, + kVoltasFanAuto, kVoltasFanAuto, kVoltasFanMed); result += addBoolToString(_.Turbo, kTurboStr); + result += addBoolToString(_.Econo, kEconoStr); result += addBoolToString(_.Wifi, kWifiStr); result += addBoolToString(_.Light, kLightStr); return result; diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index a069e2551..52cf90ec6 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -70,6 +70,16 @@ union VoltasProtocol { }; // Constants +const uint8_t kVoltasFan = 0b0001; ///< 1 +const uint8_t kVoltasHeat = 0b0010; ///< 2 +const uint8_t kVoltasDry = 0b0100; ///< 4 +const uint8_t kVoltasCool = 0b1000; ///< 8 +const uint8_t kVoltasMinTemp = 16; ///< Celsius +const uint8_t kVoltasMaxTemp = 30; ///< Celsius +const uint8_t kVoltasFanLow = 0b001; ///< 1 +const uint8_t kVoltasFanMed = 0b010; ///< 2 +const uint8_t kVoltasFanHigh = 0b100; ///< 4 +const uint8_t kVoltasFanAuto = 0b111; ///< 7 // Classes /// Class for handling detailed Voltas A/C messages. @@ -93,6 +103,8 @@ class IRVoltas { bool getPower(void) const; void on(void); void off(void); + void setWifi(const bool on); + bool getWifi(void) const; void setTemp(const uint8_t temp); uint8_t getTemp(void); void setFan(const uint8_t speed); @@ -100,7 +112,11 @@ class IRVoltas { void setMode(const uint8_t mode); uint8_t getMode(void); void setEcono(const bool on); - bool getEcono(void); + bool getEcono(void) const; + void setLight(const bool on); + bool getLight(void) const; + void setTurbo(const bool on); + bool getTurbo(void) const; void setOffTimer(const uint16_t nr_of_mins); uint16_t getOffTimer(void); uint8_t* getRaw(void); diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index bbec7e26c..597d78fd3 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -40,7 +40,8 @@ TEST(TestDecodeVoltas, RealExample) { ASSERT_EQ(kVoltasBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Turbo: Off, WiFi: On, Light: Off", + "Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (High), " + "Turbo: Off, Econo: Off, WiFi: On, Light: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -109,3 +110,125 @@ TEST(TestIRVoltasClass, Power) { ac.setPower(false); EXPECT_FALSE(ac.getPower()); } + +TEST(TestIRVoltasClass, Wifi) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setWifi(false); + EXPECT_FALSE(ac.getWifi()); + ac.setWifi(true); + EXPECT_TRUE(ac.getWifi()); + ac.setWifi(false); + EXPECT_FALSE(ac.getWifi()); +} + +TEST(TestIRVoltasClass, Turbo) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); + ac.setTurbo(true); + EXPECT_TRUE(ac.getTurbo()); + ac.setTurbo(false); + EXPECT_FALSE(ac.getTurbo()); +} + +TEST(TestIRVoltasClass, Econo) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setEcono(false); + EXPECT_FALSE(ac.getEcono()); + ac.setEcono(true); + EXPECT_TRUE(ac.getEcono()); + ac.setEcono(false); + EXPECT_FALSE(ac.getEcono()); +} + +TEST(TestIRVoltasClass, Light) { + IRVoltas 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()); +} + +TEST(TestVoltasClass, OperatingMode) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setMode(kVoltasCool); + EXPECT_EQ(kVoltasCool, ac.getMode()); + ac.setMode(kVoltasFan); + EXPECT_EQ(kVoltasFan, ac.getMode()); + ac.setMode(kVoltasDry); + EXPECT_EQ(kVoltasDry, ac.getMode()); + ac.setMode(kVoltasHeat); + EXPECT_EQ(kVoltasHeat, ac.getMode()); + + ac.setMode(kVoltasCool - 1); + EXPECT_EQ(kVoltasCool, ac.getMode()); + + ac.setMode(kVoltasCool + 1); + EXPECT_EQ(kVoltasCool, ac.getMode()); + + ac.setMode(255); + EXPECT_EQ(kVoltasCool, ac.getMode()); +} + +TEST(TestVoltasClass, Temperature) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setTemp(kVoltasMinTemp); + EXPECT_EQ(kVoltasMinTemp, ac.getTemp()); + + ac.setTemp(kVoltasMinTemp + 1); + EXPECT_EQ(kVoltasMinTemp + 1, ac.getTemp()); + + ac.setTemp(kVoltasMaxTemp); + EXPECT_EQ(kVoltasMaxTemp, ac.getTemp()); + + ac.setTemp(kVoltasMinTemp - 1); + EXPECT_EQ(kVoltasMinTemp, ac.getTemp()); + + ac.setTemp(kVoltasMaxTemp + 1); + EXPECT_EQ(kVoltasMaxTemp, ac.getTemp()); + + ac.setTemp(23); + EXPECT_EQ(23, ac.getTemp()); + + ac.setTemp(0); + EXPECT_EQ(kVoltasMinTemp, ac.getTemp()); + + ac.setTemp(255); + EXPECT_EQ(kVoltasMaxTemp, ac.getTemp()); +} + +TEST(TestVoltasClass, FanSpeed) { + IRVoltas ac(kGpioUnused); + ac.begin(); + ac.setFan(kVoltasFanLow); + + ac.setFan(kVoltasFanAuto); + EXPECT_EQ(kVoltasFanAuto, ac.getFan()); + + ac.setFan(kVoltasFanLow); + EXPECT_EQ(kVoltasFanLow, ac.getFan()); + ac.setFan(kVoltasFanMed); + EXPECT_EQ(kVoltasFanMed, ac.getFan()); + ac.setFan(kVoltasFanHigh); + EXPECT_EQ(kVoltasFanHigh, ac.getFan()); + + ac.setFan(0); + EXPECT_EQ(kVoltasFanAuto, ac.getFan()); + + ac.setFan(255); + EXPECT_EQ(kVoltasFanAuto, ac.getFan()); +} From 05d020f163d21be86ca80d2e131e1cd6b82b9280 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Aug 2020 17:50:58 +1000 Subject: [PATCH 03/16] Voltas: More detailed/improved support. * More `IRac` support. * Add swing H & V. * Proper reset state. * Set fan speed & temp for Fan & Dry modes. * Correct fan speed values. * Add unit tests for the `IRac` support. * Add unit test coverage for other changes. For #1238 --- src/IRac.cpp | 49 ++++++++++++++++++++++++ src/IRac.h | 8 ++++ src/ir_Voltas.cpp | 85 +++++++++++++++++++++++++++++++---------- src/ir_Voltas.h | 9 ++++- test/IRac_test.cpp | 39 +++++++++++++++++++ test/ir_Voltas_test.cpp | 33 +++++++++++++++- 6 files changed, 198 insertions(+), 25 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 8980433b5..220da7916 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -263,6 +263,9 @@ bool IRac::isProtocolSupported(const decode_type_t protocol) { #if SEND_VESTEL_AC case decode_type_t::VESTEL_AC: #endif +#if SEND_VOLTAS + case decode_type_t::VOLTAS: +#endif #if SEND_WHIRLPOOL_AC case decode_type_t::WHIRLPOOL_AC: #endif @@ -1886,6 +1889,43 @@ void IRac::vestel(IRVestelAc *ac, } #endif // SEND_VESTEL_AC +#if SEND_VOLTAS +/// Send a Voltas A/C message with the supplied settings. +/// @param[in, out] ac A Ptr to an IRVoltas 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] swingh The horizontal swing setting. +/// @param[in] turbo Run the device in turbo/powerful mode. +/// @param[in] econo Run the device in economical mode. +/// @param[in] light Turn on the LED/Display mode. +void IRac::voltas(IRVoltas *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light) { + 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); + ac->setSwingH(swingh != stdAc::swingh_t::kOff); + // No Quiet setting available. + ac->setTurbo(turbo); + ac->setEcono(econo); + ac->setLight(light); + // No Filter setting available. + // No Clean setting available. + // No Beep setting available. + // No Sleep setting available. + // No Clock setting available. + ac->send(); +} +#endif // SEND_VOLTAS + #if SEND_WHIRLPOOL_AC /// Send a Whirlpool A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRWhirlpoolAc object to use. @@ -2451,6 +2491,15 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { break; } #endif // SEND_VESTEL_AC +#if SEND_VOLTAS + case VOLTAS: + { + IRVoltas ac(_pin, _inverted, _modulation); + voltas(&ac, send.power, send.mode, degC, send.fanspeed, + send.swingv, send.swingh, send.turbo, send.econo, send.light); + break; + } +#endif // SEND_VOLTAS #if SEND_WHIRLPOOL_AC case WHIRLPOOL_AC: { diff --git a/src/IRac.h b/src/IRac.h index 5721f2bd6..8738492ab 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -36,6 +36,7 @@ #include "ir_Toshiba.h" #include "ir_Trotec.h" #include "ir_Vestel.h" +#include "ir_Voltas.h" #include "ir_Whirlpool.h" // Constants @@ -405,6 +406,13 @@ void electra(IRElectraAc *ac, const int16_t sleep = -1, const int16_t clock = -1, const bool sendNormal = true); #endif // SEND_VESTEL_AC +#if SEND_VOLTAS + void voltas(IRVoltas *ac, + const bool on, const stdAc::opmode_t mode, + const float degrees, const stdAc::fanspeed_t fan, + const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, + const bool turbo, const bool econo, const bool light); +#endif // SEND_VOLTAS #if SEND_WHIRLPOOL_AC void whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, const float degrees, diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index a22764015..7c0e8bee7 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -91,7 +91,10 @@ IRVoltas::IRVoltas(const uint16_t pin, const bool inverted, // Reset the internal state to a fixed known good state. void IRVoltas::stateReset() { // This resets to a known-good state. - std::memset(_.raw, 0, sizeof _.raw); + // ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-674699746 + const uint8_t kReset[kVoltasStateLength] = { + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xCB}; + setRaw(kReset); } /// Set up hardware to be able to send a message. @@ -163,8 +166,13 @@ bool IRVoltas::getPower(void) const { return _.Power; } void IRVoltas::setMode(const uint8_t mode) { switch (mode) { case kVoltasFan: - case kVoltasHeat: + setFan(kVoltasFanHigh); + break; case kVoltasDry: + setFan(kVoltasFanLow); + setTemp(kVoltasDryTemp); + break; + case kVoltasHeat: case kVoltasCool: break; default: @@ -178,6 +186,30 @@ void IRVoltas::setMode(const uint8_t mode) { /// @return The current operating mode setting. uint8_t IRVoltas::getMode(void) { return _.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 IRVoltas::convertMode(const stdAc::opmode_t mode) { + switch (mode) { + case stdAc::opmode_t::kHeat: return kVoltasHeat; + case stdAc::opmode_t::kDry: return kVoltasDry; + case stdAc::opmode_t::kFan: return kVoltasFan; + default: return kVoltasCool; + } +} + +/// 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 IRVoltas::toCommonMode(const uint8_t mode) { + switch (mode) { + case kVoltasHeat: return stdAc::opmode_t::kHeat; + case kVoltasDry: return stdAc::opmode_t::kDry; + case kVoltasFan: return stdAc::opmode_t::kFan; + default: return stdAc::opmode_t::kCool; + } +} + /// Set the temperature. /// @param[in] temp The temperature in degrees celsius. void IRVoltas::setTemp(const uint8_t temp) { @@ -186,6 +218,10 @@ void IRVoltas::setTemp(const uint8_t temp) { _.Temp = new_temp - kVoltasMinTemp; } +/// Get the current temperature setting. +/// @return The current setting for temp. in degrees celsius. +uint8_t IRVoltas::getTemp(void) { return _.Temp + kVoltasMinTemp; } + /// Set the speed of the fan. /// @param[in] fan The desired setting. void IRVoltas::setFan(const uint8_t fan) { @@ -201,6 +237,10 @@ void IRVoltas::setFan(const uint8_t fan) { } } +/// Get the current fan speed setting. +/// @return The current fan speed/mode. +uint8_t IRVoltas::getFan(void) { return _.FanSpeed; } + /// 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. @@ -227,13 +267,21 @@ stdAc::fanspeed_t IRVoltas::toCommonFanSpeed(const uint8_t spd) { } } -/// Get the current fan speed setting. -/// @return The current fan speed/mode. -uint8_t IRVoltas::getFan(void) { return _.FanSpeed; } +/// Set the Vertical Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setSwingV(const bool on) { _.SwingV = on ? 0b111 : 0b000; } -/// Get the current temperature setting. -/// @return The current setting for temp. in degrees celsius. -uint8_t IRVoltas::getTemp(void) { return _.Temp + kVoltasMinTemp; } +/// Get the Vertical Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSwingV(void) const { return _.SwingV == 0b111; } + +/// Set the Horizontal Swing setting of the A/C. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setSwingH(const bool on) { _.SwingH = on; } + +/// Get the Horizontal Swing setting of the A/C. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSwingH(void) const { return _.SwingH; } /// Change the Wifi setting. /// @param[in] on true, the setting is on. false, the setting is off. @@ -273,29 +321,22 @@ stdAc::state_t IRVoltas::toCommon() { stdAc::state_t result; result.protocol = decode_type_t::VOLTAS; result.power = _.Power; - // result.mode = toCommonMode(getMode()); + result.mode = toCommonMode(_.Mode); result.celsius = true; result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.FanSpeed); - /* - if (getSwingVerticalAuto()) - result.swingv = stdAc::swingv_t::kAuto; - else - result.swingv = toCommonSwingV(getSwingVerticalPosition()); - */ + result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; result.turbo = _.Turbo; result.econo = _.Econo; result.light = _.Light; - /* - result.clean = getXFan(); - result.sleep = getSleep() ? 0 : -1; - */ // Not supported. result.model = -1; - result.swingh = stdAc::swingh_t::kOff; result.quiet = false; result.filter = false; + result.clean = false; result.beep = false; + result.sleep = -1; result.clock = -1; return result; } @@ -304,13 +345,15 @@ stdAc::state_t IRVoltas::toCommon() { /// @return A human readable string. String IRVoltas::toString() { String result = ""; - result.reserve(100); // Reserve some heap for the string to reduce fragging. + result.reserve(120); // Reserve some heap for the string to reduce fragging. result += addBoolToString(_.Power, kPowerStr, false); result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, kVoltasDry, kVoltasFan); result += addTempToString(getTemp()); result += addFanToString(_.FanSpeed, kVoltasFanHigh, kVoltasFanLow, kVoltasFanAuto, kVoltasFanAuto, kVoltasFanMed); + result += addBoolToString(getSwingV(), kSwingVStr); + result += addBoolToString(_.SwingH, kSwingHStr); result += addBoolToString(_.Turbo, kTurboStr); result += addBoolToString(_.Econo, kEconoStr); result += addBoolToString(_.Wifi, kWifiStr); diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index 52cf90ec6..e712fb26f 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -75,10 +75,11 @@ const uint8_t kVoltasHeat = 0b0010; ///< 2 const uint8_t kVoltasDry = 0b0100; ///< 4 const uint8_t kVoltasCool = 0b1000; ///< 8 const uint8_t kVoltasMinTemp = 16; ///< Celsius +const uint8_t kVoltasDryTemp = 24; ///< Celsius const uint8_t kVoltasMaxTemp = 30; ///< Celsius -const uint8_t kVoltasFanLow = 0b001; ///< 1 +const uint8_t kVoltasFanHigh = 0b001; ///< 1 const uint8_t kVoltasFanMed = 0b010; ///< 2 -const uint8_t kVoltasFanHigh = 0b100; ///< 4 +const uint8_t kVoltasFanLow = 0b100; ///< 4 const uint8_t kVoltasFanAuto = 0b111; ///< 7 // Classes @@ -111,6 +112,10 @@ class IRVoltas { uint8_t getFan(void); void setMode(const uint8_t mode); uint8_t getMode(void); + void setSwingH(const bool on); + bool getSwingH(void) const; + void setSwingV(const bool on); + bool getSwingV(void) const; void setEcono(const bool on); bool getEcono(void) const; void setLight(const bool on); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 6b8501384..eb3428caf 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -29,6 +29,7 @@ #include "ir_Toshiba.h" #include "ir_Trotec.h" #include "ir_Vestel.h" +#include "ir_Voltas.h" #include "ir_Whirlpool.h" #include "IRac.h" #include "IRrecv.h" @@ -1554,6 +1555,44 @@ TEST(TestIRac, Vestel) { "m520s100000", ac._irsend.outputStr()); } +TEST(TestIRac, Voltas) { + IRVoltas ac(kGpioUnused); + IRac irac(kGpioUnused); + IRrecv capture(kGpioUnused); + char expected[] = + "Power: On, Mode: 8 (Cool), Temp: 18C, Fan: 1 (High), " + "Swing(V): On, Swing(H): On, " + "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; + + ac.begin(); + irac.voltas(&ac, + true, // Power + stdAc::opmode_t::kCool, // Mode + 18, // Celsius + stdAc::fanspeed_t::kHigh, // Fan speed + stdAc::swingv_t::kAuto, // Vertical Swing + stdAc::swingh_t::kAuto, // Horizontal Swing + false, // Turbo + false, // Econo + true); // Light + EXPECT_TRUE(ac.getPower()); + EXPECT_EQ(kVoltasCool, ac.getMode()); + EXPECT_EQ(18, ac.getTemp()); + EXPECT_EQ(kVoltasFanHigh, ac.getFan()); + EXPECT_FALSE(ac.getTurbo()); + EXPECT_FALSE(ac.getEcono()); + EXPECT_TRUE(ac.getLight()); + ASSERT_EQ(expected, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); +/* TODO(crankyoldgit): Fix/enable this. + ASSERT_EQ(VOLTAS, ac._irsend.capture.decode_type); + ASSERT_EQ(kVoltasBits, 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, Whirlpool) { IRWhirlpoolAc ac(kGpioUnused); diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 597d78fd3..5a0e223b9 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -40,7 +40,8 @@ TEST(TestDecodeVoltas, RealExample) { ASSERT_EQ(kVoltasBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (High), " + "Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " + "Swing(V): Off, Swing(H): On, " "Turbo: Off, Econo: Off, WiFi: On, Light: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; @@ -68,7 +69,7 @@ TEST(TestUtils, Housekeeping) { ASSERT_EQ("VOLTAS", typeToString(decode_type_t::VOLTAS)); ASSERT_EQ(decode_type_t::VOLTAS, strToDecodeType("VOLTAS")); ASSERT_TRUE(hasACState(decode_type_t::VOLTAS)); - ASSERT_FALSE(IRac::isProtocolSupported(decode_type_t::VOLTAS)); + ASSERT_TRUE(IRac::isProtocolSupported(decode_type_t::VOLTAS)); ASSERT_EQ(kVoltasBits, IRsend::defaultBits(decode_type_t::VOLTAS)); ASSERT_EQ(kNoRepeat, IRsend::minRepeats(decode_type_t::VOLTAS)); } @@ -232,3 +233,31 @@ TEST(TestVoltasClass, FanSpeed) { ac.setFan(255); EXPECT_EQ(kVoltasFanAuto, ac.getFan()); } + +TEST(TestVoltasClass, SwingV) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setSwingV(true); + EXPECT_TRUE(ac.getSwingV()); + + ac.setSwingV(false); + EXPECT_EQ(false, ac.getSwingV()); + + ac.setSwingV(true); + EXPECT_TRUE(ac.getSwingV()); +} + +TEST(TestVoltasClass, SwingH) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setSwingH(true); + EXPECT_TRUE(ac.getSwingH()); + + ac.setSwingH(false); + EXPECT_EQ(false, ac.getSwingH()); + + ac.setSwingH(true); + EXPECT_TRUE(ac.getSwingH()); +} From 0aeeeff25b25d9f8a4f49b075602ccd344f219d2 Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Aug 2020 15:10:47 +1000 Subject: [PATCH 04/16] Voltas: Support the SwingH "change" bits. * Certain bits in the first byte need to be set if we are adjusting the SwingH setting. * Handle when we don't have knowledge of a swingh state by using the previous state (if available). Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-674905734 --- src/IRac.cpp | 4 ++-- src/ir_Voltas.cpp | 39 +++++++++++++++++++++++++++++++++++---- src/ir_Voltas.h | 8 ++++++-- test/ir_Voltas_test.cpp | 8 +++++++- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 220da7916..56bd75b08 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -3572,9 +3572,9 @@ namespace IRAcUtils { #endif // DECODE_VESTEL_AC #if DECODE_VOLTAS case decode_type_t::VOLTAS: { - IRVestelAc ac(kGpioUnused); + IRVoltas ac(kGpioUnused); ac.setRaw(decode->state); - *result = ac.toCommon(); + *result = ac.toCommon(prev); break; } #endif // DECODE_VOLTAS diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 7c0e8bee7..e3b50ed62 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -15,6 +15,7 @@ using irutils::addBoolToString; using irutils::addModeToString; using irutils::addFanToString; +using irutils::addLabeledString; using irutils::addTempToString; using irutils::minsToString; @@ -277,12 +278,29 @@ bool IRVoltas::getSwingV(void) const { return _.SwingV == 0b111; } /// Set the Horizontal Swing setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. -void IRVoltas::setSwingH(const bool on) { _.SwingH = on; } +void IRVoltas::setSwingH(const bool on) { + _.SwingH = on; + setSwingHChange(true); +} /// Get the Horizontal Swing setting of the A/C. /// @return true, the setting is on. false, the setting is off. bool IRVoltas::getSwingH(void) const { return _.SwingH; } +/// Set the bits for changing the Horizontal Swing setting of the A/C. +/// @param[in] on true, the change bits are set. +/// false, the "no change" bits are set. +void IRVoltas::setSwingHChange(const bool on) { + _.SwingHChange = on ? kVoltasSwingHChange : kVoltasSwingHNoChange; + if (!on) _.SwingH = true; // "No Change" also sets SwingH to 1. +} + +/// Are the Horizontal Swing change bits set in the message? +/// @return true, the correct bits are set. false, the correct bits are not set. +bool IRVoltas::getSwingHChange(void) const { + return _.SwingHChange == kVoltasSwingHChange; +} + /// Change the Wifi setting. /// @param[in] on true, the setting is on. false, the setting is off. void IRVoltas::setWifi(const bool on) { _.Wifi = on; } @@ -316,9 +334,18 @@ void IRVoltas::setLight(const bool on) { _.Light = on; } bool IRVoltas::getLight(void) const { return _.Light; } /// Convert the current internal state into its stdAc::state_t equivilant. +/// @param[in] prev Ptr to the previous state if available. /// @return The stdAc equivilant of the native settings. -stdAc::state_t IRVoltas::toCommon() { +stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { stdAc::state_t result; + // Start with the previous state if given it. + if (prev != NULL) { + result = *prev; + } else { + // Set defaults for non-zero values that are not implicitly set for when + // there is no previous state. + result.swingh = stdAc::swingh_t::kOff; + } result.protocol = decode_type_t::VOLTAS; result.power = _.Power; result.mode = toCommonMode(_.Mode); @@ -326,7 +353,8 @@ stdAc::state_t IRVoltas::toCommon() { result.degrees = getTemp(); result.fanspeed = toCommonFanSpeed(_.FanSpeed); result.swingv = getSwingV() ? stdAc::swingv_t::kAuto : stdAc::swingv_t::kOff; - result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; + if (getSwingHChange()) + result.swingh = _.SwingH ? stdAc::swingh_t::kAuto : stdAc::swingh_t::kOff; result.turbo = _.Turbo; result.econo = _.Econo; result.light = _.Light; @@ -353,7 +381,10 @@ String IRVoltas::toString() { result += addFanToString(_.FanSpeed, kVoltasFanHigh, kVoltasFanLow, kVoltasFanAuto, kVoltasFanAuto, kVoltasFanMed); result += addBoolToString(getSwingV(), kSwingVStr); - result += addBoolToString(_.SwingH, kSwingHStr); + if (getSwingHChange()) + result += addBoolToString(_.SwingH, kSwingHStr); + else + result += addLabeledString(kNAStr, kSwingHStr); result += addBoolToString(_.Turbo, kTurboStr); result += addBoolToString(_.Econo, kEconoStr); result += addBoolToString(_.Wifi, kWifiStr); diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index e712fb26f..18c271312 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -30,7 +30,7 @@ union VoltasProtocol { struct { // Byte 0 uint8_t SwingH :1; - uint8_t Unknown0 :7; + uint8_t SwingHChange :7; // Byte 1 uint8_t Mode :4; uint8_t :1; @@ -81,6 +81,8 @@ const uint8_t kVoltasFanHigh = 0b001; ///< 1 const uint8_t kVoltasFanMed = 0b010; ///< 2 const uint8_t kVoltasFanLow = 0b100; ///< 4 const uint8_t kVoltasFanAuto = 0b111; ///< 7 +const uint8_t kVoltasSwingHChange = 0b1111100; ///< 0x7D +const uint8_t kVoltasSwingHNoChange = 0b0011001; ///< 0x19 // Classes /// Class for handling detailed Voltas A/C messages. @@ -114,6 +116,8 @@ class IRVoltas { uint8_t getMode(void); void setSwingH(const bool on); bool getSwingH(void) const; + void setSwingHChange(const bool on); + bool getSwingHChange(void) const; void setSwingV(const bool on); bool getSwingV(void) const; void setEcono(const bool on); @@ -130,7 +134,7 @@ class IRVoltas { 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); + stdAc::state_t toCommon(const stdAc::state_t *prev = NULL); String toString(void); #ifndef UNIT_TEST diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 5a0e223b9..8ba317b3f 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -41,7 +41,7 @@ TEST(TestDecodeVoltas, RealExample) { EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( "Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " - "Swing(V): Off, Swing(H): On, " + "Swing(V): Off, Swing(H): N/A, " "Turbo: Off, Econo: Off, WiFi: On, Light: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; @@ -251,13 +251,19 @@ TEST(TestVoltasClass, SwingV) { TEST(TestVoltasClass, SwingH) { IRVoltas ac(kGpioUnused); ac.begin(); + ac.setSwingHChange(false); + EXPECT_FALSE(ac.getSwingHChange()); ac.setSwingH(true); EXPECT_TRUE(ac.getSwingH()); + EXPECT_TRUE(ac.getSwingHChange()); + ac.setSwingHChange(false); ac.setSwingH(false); EXPECT_EQ(false, ac.getSwingH()); + EXPECT_TRUE(ac.getSwingHChange()); ac.setSwingH(true); EXPECT_TRUE(ac.getSwingH()); + EXPECT_TRUE(ac.getSwingHChange()); } From 95261b61d9beb7364de2e5437cd7d8f88835500a Mon Sep 17 00:00:00 2001 From: David Date: Sun, 23 Aug 2020 22:19:26 +1000 Subject: [PATCH 05/16] Voltas: Fix bug in `checksum()` * Fix an off-by-one error. * Additional tests for `get/setLight` * Uncomment disabled test in IRac_test which this change fixes. For #1238 --- src/ir_Voltas.cpp | 2 +- test/IRac_test.cpp | 2 -- test/ir_Voltas_test.cpp | 19 ++++++++++++++++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index e3b50ed62..5ea07bb66 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -124,7 +124,7 @@ void IRVoltas::setRaw(const uint8_t new_code[]) { /// Calculate and set the checksum values for the internal state. void IRVoltas::checksum(void) { - _.Checksum = calcChecksum(_.raw, kVoltasStateLength - 1); + _.Checksum = calcChecksum(_.raw); } /// Verify the checksum is valid for a given state. diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index eb3428caf..93ba8d42f 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1585,13 +1585,11 @@ TEST(TestIRac, Voltas) { ASSERT_EQ(expected, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); -/* TODO(crankyoldgit): Fix/enable this. ASSERT_EQ(VOLTAS, ac._irsend.capture.decode_type); ASSERT_EQ(kVoltasBits, 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, Whirlpool) { diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 8ba317b3f..1005ab007 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -80,13 +80,17 @@ TEST(TestIRVoltasClass, Checksums) { EXPECT_TRUE(IRVoltas::validChecksum(valid)); EXPECT_FALSE(IRVoltas::validChecksum(valid, kVoltasStateLength - 1)); EXPECT_EQ(0xE6, IRVoltas::calcChecksum(valid)); + const uint8_t badchecksum[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0x00}; + EXPECT_FALSE(IRVoltas::validChecksum(badchecksum)); + EXPECT_EQ(0xE6, IRVoltas::calcChecksum(badchecksum)); } TEST(TestIRVoltasClass, SetandGetRaw) { const uint8_t valid[kVoltasStateLength] = { 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; const uint8_t badchecksum[kVoltasStateLength] = { - 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0x00}; IRVoltas ac(kGpioUnused); ac.setRaw(valid); @@ -158,6 +162,19 @@ TEST(TestIRVoltasClass, Light) { EXPECT_TRUE(ac.getLight()); ac.setLight(false); EXPECT_FALSE(ac.getLight()); + + const uint8_t light_off[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xE6}; + ac.setRaw(light_off); + EXPECT_FALSE(ac.getLight()); + const uint8_t light_on[kVoltasStateLength] = { + 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x20, 0xC6}; + ac.setRaw(light_on); + EXPECT_TRUE(ac.getLight()); + ac.setLight(false); + EXPECT_STATE_EQ(light_off, ac.getRaw(), kVoltasBits); + ac.setLight(true); + EXPECT_STATE_EQ(light_on, ac.getRaw(), kVoltasBits); } TEST(TestVoltasClass, OperatingMode) { From 479bbb14c325163a309174ea9240df2cd92eee61 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 01:07:57 +1000 Subject: [PATCH 06/16] Voltas: Add model support * Add `set/getModel()` * Model 122LZF can't have SwingH changed, shouldn't produce an ignored message. * Update unit tests and `IRac` accordingly. * Add missing LG models in `strToModel()` Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679039592 --- src/IRac.cpp | 16 ++++++++-- src/IRac.h | 2 +- src/IRsend.h | 6 ++++ src/IRutils.cpp | 6 ++++ src/ir_Voltas.cpp | 53 +++++++++++++++++++++++++++++--- src/ir_Voltas.h | 3 ++ test/IRac_test.cpp | 67 +++++++++++++++++++++++++++++++---------- test/ir_Voltas_test.cpp | 18 +++++++++-- 8 files changed, 145 insertions(+), 26 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index 56bd75b08..f65072ecd 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -1892,6 +1892,7 @@ void IRac::vestel(IRVestelAc *ac, #if SEND_VOLTAS /// Send a Voltas A/C message with the supplied settings. /// @param[in, out] ac A Ptr to an IRVoltas object to use. +/// @param[in] model The A/C model to use. /// @param[in] on The power setting. /// @param[in] mode The operation mode setting. /// @param[in] degrees The temperature setting in degrees. @@ -1902,11 +1903,13 @@ void IRac::vestel(IRVestelAc *ac, /// @param[in] econo Run the device in economical mode. /// @param[in] light Turn on the LED/Display mode. void IRac::voltas(IRVoltas *ac, + const voltas_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, const bool turbo, const bool econo, const bool light) { ac->begin(); + ac->setModel(model); ac->setPower(on); ac->setMode(ac->convertMode(mode)); ac->setTemp(degrees); @@ -2495,8 +2498,9 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { case VOLTAS: { IRVoltas ac(_pin, _inverted, _modulation); - voltas(&ac, send.power, send.mode, degC, send.fanspeed, - send.swingv, send.swingh, send.turbo, send.econo, send.light); + voltas(&ac, (voltas_ac_remote_model_t)send.model, send.power, send.mode, + degC, send.fanspeed, send.swingv, send.swingh, send.turbo, + send.econo, send.light); break; } #endif // SEND_VOLTAS @@ -2716,6 +2720,11 @@ int16_t IRac::strToModel(const char *str, const int16_t def) { return fujitsu_ac_remote_model_t::ARJW2; } else if (!strcasecmp(str, "ARRY4")) { return fujitsu_ac_remote_model_t::ARRY4; + // LG A/C models + } else if (!strcasecmp(str, "GE6711AR2853M")) { + return lg_ac_remote_model_t::GE6711AR2853M; + } else if (!strcasecmp(str, "AKB75215403")) { + return lg_ac_remote_model_t::AKB75215403; // Panasonic A/C families } else if (!strcasecmp(str, "LKE") || !strcasecmp(str, "PANASONICLKE")) { return panasonic_ac_remote_model_t::kPanasonicLke; @@ -2730,6 +2739,9 @@ int16_t IRac::strToModel(const char *str, const int16_t def) { return panasonic_ac_remote_model_t::kPanasonicCkp; } else if (!strcasecmp(str, "RKR") || !strcasecmp(str, "PANASONICRKR")) { return panasonic_ac_remote_model_t::kPanasonicRkr; + // Voltas A/C models + } else if (!strcasecmp(str, "122LZF")) { + return voltas_ac_remote_model_t::kVoltas122LZF; // Whirlpool A/C models } else if (!strcasecmp(str, "DG11J13A") || !strcasecmp(str, "DG11J104") || !strcasecmp(str, "DG11J1-04")) { diff --git a/src/IRac.h b/src/IRac.h index 8738492ab..d1e7247b4 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -407,7 +407,7 @@ void electra(IRElectraAc *ac, const bool sendNormal = true); #endif // SEND_VESTEL_AC #if SEND_VOLTAS - void voltas(IRVoltas *ac, + void voltas(IRVoltas *ac, const voltas_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, diff --git a/src/IRsend.h b/src/IRsend.h index 57b088533..76fd5cdae 100644 --- a/src/IRsend.h +++ b/src/IRsend.h @@ -148,6 +148,12 @@ enum panasonic_ac_remote_model_t { kPanasonicRkr = 6, }; +/// Voltas A/C model numbers +enum voltas_ac_remote_model_t { + kVoltasUnknown = 0, // Full Function + kVoltas122LZF = 1, // (1) 122LZF (No SwingH support) (Default) +}; + /// Whirlpool A/C model numbers enum whirlpool_ac_remote_model_t { DG11J13A = 1, // DG11J1-04 too diff --git a/src/IRutils.cpp b/src/IRutils.cpp index 25a1fe13a..33eab5474 100644 --- a/src/IRutils.cpp +++ b/src/IRutils.cpp @@ -544,6 +544,12 @@ namespace irutils { default: return kUnknownStr; } break; + case decode_type_t::VOLTAS: + switch (model) { + case voltas_ac_remote_model_t::kVoltas122LZF: return F("122LZF"); + default: return kUnknownStr; + } + break; case decode_type_t::WHIRLPOOL_AC: switch (model) { case whirlpool_ac_remote_model_t::DG11J13A: return F("DG11J13A"); diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 5ea07bb66..9df5d61db 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -13,6 +13,7 @@ #include "IRutils.h" using irutils::addBoolToString; +using irutils::addModelToString; using irutils::addModeToString; using irutils::addFanToString; using irutils::addLabeledString; @@ -109,6 +110,34 @@ void IRVoltas::send(const uint16_t repeat) { } #endif // SEND_VOLTAS +/// Get the model information currently known. +/// @param[in] raw Work out the model info from the current raw state. +/// @return The known model number. +voltas_ac_remote_model_t IRVoltas::getModel(const bool raw) const { + if (raw) { + switch (_.SwingHChange) { + case kVoltasSwingHNoChange: + return voltas_ac_remote_model_t::kVoltas122LZF; + default: + return voltas_ac_remote_model_t::kVoltasUnknown; + } + } else { + return _model; + } +} + +/// Set the current model for the remote. +/// @param[in] model The model number. +void IRVoltas::setModel(const voltas_ac_remote_model_t model) { + switch (model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + _model = model; + setSwingHChange(false); + break; + default: _model = voltas_ac_remote_model_t::kVoltasUnknown; + } +} + /// 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* IRVoltas::getRaw(void) { @@ -120,6 +149,7 @@ uint8_t* IRVoltas::getRaw(void) { /// @param[in] new_code A valid code for this protocol. void IRVoltas::setRaw(const uint8_t new_code[]) { std::memcpy(_.raw, new_code, kVoltasStateLength); + setModel(getModel(true)); } /// Calculate and set the checksum values for the internal state. @@ -279,13 +309,25 @@ bool IRVoltas::getSwingV(void) const { return _.SwingV == 0b111; } /// Set the Horizontal Swing setting of the A/C. /// @param[in] on true, the setting is on. false, the setting is off. void IRVoltas::setSwingH(const bool on) { - _.SwingH = on; - setSwingHChange(true); + switch (_model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + break; // unsupported on these models. + default: + _.SwingH = on; + setSwingHChange(true); + } } /// Get the Horizontal Swing setting of the A/C. /// @return true, the setting is on. false, the setting is off. -bool IRVoltas::getSwingH(void) const { return _.SwingH; } +bool IRVoltas::getSwingH(void) const { + switch (_model) { + case voltas_ac_remote_model_t::kVoltas122LZF: + return false; // unsupported on these models. + default: + return _.SwingH; + } +} /// Set the bits for changing the Horizontal Swing setting of the A/C. /// @param[in] on true, the change bits are set. @@ -346,6 +388,7 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { // there is no previous state. result.swingh = stdAc::swingh_t::kOff; } + result.model = getModel(); result.protocol = decode_type_t::VOLTAS; result.power = _.Power; result.mode = toCommonMode(_.Mode); @@ -359,7 +402,6 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { result.econo = _.Econo; result.light = _.Light; // Not supported. - result.model = -1; result.quiet = false; result.filter = false; result.clean = false; @@ -374,7 +416,8 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { String IRVoltas::toString() { String result = ""; result.reserve(120); // Reserve some heap for the string to reduce fragging. - result += addBoolToString(_.Power, kPowerStr, false); + result += addModelToString(decode_type_t::VOLTAS, getModel(), false); + result += addBoolToString(_.Power, kPowerStr); result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, kVoltasDry, kVoltasFan); result += addTempToString(getTemp()); diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index 18c271312..dc198c04a 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -102,6 +102,8 @@ class IRVoltas { void begin(); static bool validChecksum(const uint8_t state[], const uint16_t length = kVoltasStateLength); + void setModel(const voltas_ac_remote_model_t model); + voltas_ac_remote_model_t getModel(const bool raw = false) const; void setPower(const bool on); bool getPower(void) const; void on(void); @@ -146,6 +148,7 @@ class IRVoltas { /// @endcond #endif VoltasProtocol _; ///< The state of the IR remote. + voltas_ac_remote_model_t _model; ///< Model type. void checksum(void); static uint8_t calcChecksum(const uint8_t state[], const uint16_t length = kVoltasStateLength); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index 93ba8d42f..d96afb316 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1559,22 +1559,25 @@ TEST(TestIRac, Voltas) { IRVoltas ac(kGpioUnused); IRac irac(kGpioUnused); IRrecv capture(kGpioUnused); - char expected[] = - "Power: On, Mode: 8 (Cool), Temp: 18C, Fan: 1 (High), " - "Swing(V): On, Swing(H): On, " - "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; - ac.begin(); + + // Test the UNKNOWN model type + char expected_unknown[] = + "Model: 0 (UNKNOWN), Power: On, Mode: 8 (Cool), Temp: 18C, " + "Fan: 1 (High), Swing(V): On, Swing(H): On, " + "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; irac.voltas(&ac, - true, // Power - stdAc::opmode_t::kCool, // Mode - 18, // Celsius - stdAc::fanspeed_t::kHigh, // Fan speed - stdAc::swingv_t::kAuto, // Vertical Swing - stdAc::swingh_t::kAuto, // Horizontal Swing - false, // Turbo - false, // Econo - true); // Light + voltas_ac_remote_model_t::kVoltasUnknown, // Model + true, // Power + stdAc::opmode_t::kCool, // Mode + 18, // Celsius + stdAc::fanspeed_t::kHigh, // Fan speed + stdAc::swingv_t::kAuto, // Vertical Swing + stdAc::swingh_t::kAuto, // Horizontal Swing + false, // Turbo + false, // Econo + true); // Light + EXPECT_EQ(voltas_ac_remote_model_t::kVoltasUnknown, ac.getModel()); EXPECT_TRUE(ac.getPower()); EXPECT_EQ(kVoltasCool, ac.getMode()); EXPECT_EQ(18, ac.getTemp()); @@ -1582,14 +1585,46 @@ TEST(TestIRac, Voltas) { EXPECT_FALSE(ac.getTurbo()); EXPECT_FALSE(ac.getEcono()); EXPECT_TRUE(ac.getLight()); - ASSERT_EQ(expected, ac.toString()); + ASSERT_EQ(expected_unknown, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); ASSERT_EQ(VOLTAS, ac._irsend.capture.decode_type); ASSERT_EQ(kVoltasBits, ac._irsend.capture.bits); - ASSERT_EQ(expected, IRAcUtils::resultAcToString(&ac._irsend.capture)); + ASSERT_EQ(expected_unknown, IRAcUtils::resultAcToString(&ac._irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&ac._irsend.capture, &r, &p)); + + ac._irsend.reset(); + // Test the UNKNOWN model type + char expected_122LZF[] = + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 18C, " + "Fan: 1 (High), Swing(V): On, Swing(H): N/A, " + "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; + irac.voltas(&ac, + voltas_ac_remote_model_t::kVoltas122LZF, // Model + true, // Power + stdAc::opmode_t::kCool, // Mode + 18, // Celsius + stdAc::fanspeed_t::kHigh, // Fan speed + stdAc::swingv_t::kAuto, // Vertical Swing + stdAc::swingh_t::kAuto, // Horizontal Swing + false, // Turbo + false, // Econo + true); // Light + EXPECT_EQ(voltas_ac_remote_model_t::kVoltas122LZF, ac.getModel()); + EXPECT_TRUE(ac.getPower()); + EXPECT_EQ(kVoltasCool, ac.getMode()); + EXPECT_EQ(18, ac.getTemp()); + EXPECT_EQ(kVoltasFanHigh, ac.getFan()); + EXPECT_FALSE(ac.getTurbo()); + EXPECT_FALSE(ac.getEcono()); + EXPECT_TRUE(ac.getLight()); + ASSERT_EQ(expected_122LZF, ac.toString()); + ac._irsend.makeDecodeResult(); + EXPECT_TRUE(capture.decode(&ac._irsend.capture)); + ASSERT_EQ(VOLTAS, ac._irsend.capture.decode_type); + ASSERT_EQ(kVoltasBits, ac._irsend.capture.bits); + ASSERT_EQ(expected_122LZF, IRAcUtils::resultAcToString(&ac._irsend.capture)); } TEST(TestIRac, Whirlpool) { diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 1005ab007..be9c1d2fe 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -40,7 +40,7 @@ TEST(TestDecodeVoltas, RealExample) { ASSERT_EQ(kVoltasBits, irsend.capture.bits); EXPECT_STATE_EQ(expected, irsend.capture.state, irsend.capture.bits); EXPECT_EQ( - "Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " + "Model: 1 (122LZF), Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " "Swing(V): Off, Swing(H): N/A, " "Turbo: Off, Econo: Off, WiFi: On, Light: Off", IRAcUtils::resultAcToString(&irsend.capture)); @@ -268,6 +268,9 @@ TEST(TestVoltasClass, SwingV) { TEST(TestVoltasClass, SwingH) { IRVoltas ac(kGpioUnused); ac.begin(); + // This model allows full control. + ac.setModel(voltas_ac_remote_model_t::kVoltasUnknown); + ac.setSwingHChange(false); EXPECT_FALSE(ac.getSwingHChange()); @@ -277,10 +280,21 @@ TEST(TestVoltasClass, SwingH) { ac.setSwingHChange(false); ac.setSwingH(false); - EXPECT_EQ(false, ac.getSwingH()); + EXPECT_FALSE(ac.getSwingH()); EXPECT_TRUE(ac.getSwingHChange()); ac.setSwingH(true); EXPECT_TRUE(ac.getSwingH()); EXPECT_TRUE(ac.getSwingHChange()); + + // Switch to a model that does not allow SwingH control. + ac.setModel(voltas_ac_remote_model_t::kVoltas122LZF); + EXPECT_FALSE(ac.getSwingHChange()); + EXPECT_FALSE(ac.getSwingH()); + ac.setSwingH(true); + EXPECT_FALSE(ac.getSwingHChange()); + EXPECT_FALSE(ac.getSwingH()); + ac.setSwingH(false); + EXPECT_FALSE(ac.getSwingHChange()); + EXPECT_FALSE(ac.getSwingH()); } From eda9f9fe80f5e6353709e873c9ff2501e5d9405c Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 11:25:57 +1000 Subject: [PATCH 07/16] Voltas: Limit Econo to Cool mode only. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679279323 --- src/ir_Voltas.cpp | 9 ++++++++- test/ir_Voltas_test.cpp | 8 +++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 9df5d61db..18ef6c809 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -211,6 +211,7 @@ void IRVoltas::setMode(const uint8_t mode) { return; } _.Mode = mode; + setEcono(getEcono()); // Reset the econo setting if needed. } /// Get the operating mode setting of the A/C. @@ -361,7 +362,13 @@ bool IRVoltas::getTurbo(void) const { return _.Turbo; } /// Change the Econo setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRVoltas::setEcono(const bool on) { _.Econo = on; } +void IRVoltas::setEcono(const bool on) { + // The econo setting is only available in cool mode. + if (on && _.Mode == kVoltasCool) + _.Econo = true; + else + _.Econo = false; +} /// Get the value of the current Econo setting. /// @return true, the setting is on. false, the setting is off. diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index be9c1d2fe..90bac39fe 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -143,13 +143,19 @@ TEST(TestIRVoltasClass, Turbo) { TEST(TestIRVoltasClass, Econo) { IRVoltas ac(kGpioUnused); ac.begin(); - + // Control of econo mode is only available in cool. + ac.setMode(kVoltasCool); 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(kVoltasHeat); // Control of econo mode should now be disabled. + EXPECT_FALSE(ac.getEcono()); + ac.setEcono(true); + EXPECT_FALSE(ac.getEcono()); } TEST(TestIRVoltasClass, Light) { From d6dd0bc99a174d70df6628830cff1514d4ffc240 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 11:43:53 +1000 Subject: [PATCH 08/16] Voltas: Add sleep mode support. * `set/getSleep()` added. * Add & update unit tests accordingly. * Update incorrect Doxygen comment for `sleep` in `IRac` class. --- src/IRac.cpp | 28 +++++++++++++++------------- src/IRac.h | 3 ++- src/ir_Voltas.cpp | 13 +++++++++++-- src/ir_Voltas.h | 4 +++- test/IRac_test.cpp | 12 ++++++++---- test/ir_Voltas_test.cpp | 14 +++++++++++++- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/IRac.cpp b/src/IRac.cpp index f65072ecd..5e0a700aa 100644 --- a/src/IRac.cpp +++ b/src/IRac.cpp @@ -551,7 +551,7 @@ void IRac::daikin(IRDaikinESP *ac, /// @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] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. void IRac::daikin128(IRDaikin128 *ac, const bool on, const stdAc::opmode_t mode, @@ -675,7 +675,7 @@ void IRac::daikin176(IRDaikin176 *ac, /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. /// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc /// @param[in] beep Enable/Disable beeps when receiving IR messages. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. void IRac::daikin2(IRDaikin2 *ac, const bool on, const stdAc::opmode_t mode, @@ -744,7 +744,7 @@ void IRac::daikin216(IRDaikin216 *ac, /// @param[in] swingv The vertical swing setting. /// @param[in] quiet Run the device in quiet/silent mode. /// @param[in] turbo Run the device in turbo/powerful mode. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. void IRac::daikin64(IRDaikin64 *ac, const bool on, const stdAc::opmode_t mode, @@ -1458,7 +1458,7 @@ void IRac::mitsubishiHeavy88(IRMitsubishiHeavy88Ac *ac, /// @param[in] econo Run the device in economical mode. /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. /// @param[in] clean Turn on the self-cleaning mode. e.g. Mould, dry filters etc -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::mitsubishiHeavy152(IRMitsubishiHeavy152Ac *ac, const bool on, const stdAc::opmode_t mode, const float degrees, @@ -1500,7 +1500,7 @@ void IRac::mitsubishiHeavy152(IRMitsubishiHeavy152Ac *ac, /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] light Turn on the LED/Display mode. /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::neoclima(IRNeoclimaAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, @@ -1625,7 +1625,7 @@ void IRac::samsung(IRSamsungAc *ac, /// @param[in] fan The speed setting for the fan. /// @param[in] swingv The vertical swing setting. /// @param[in] beep Enable/Disable beeps when receiving IR messages. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::sanyo(IRSanyoAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, @@ -1755,7 +1755,7 @@ void IRac::tcl112(IRTcl112Ac *ac, /// @param[in] fan The speed setting for the fan. /// @param[in] swingv The vertical swing setting. /// @param[in] light Turn on the LED/Display mode. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::teco(IRTecoAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, @@ -1823,7 +1823,7 @@ void IRac::toshiba(IRToshibaAC *ac, /// @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] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::trotec(IRTrotecESP *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, @@ -1857,7 +1857,7 @@ void IRac::trotec(IRTrotecESP *ac, /// @param[in] swingv The vertical swing setting. /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] filter Turn on the (ion/pollen/etc) filter mode. -/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. /// @param[in] sendNormal Do we send a Normal settings message at all? /// i.e In addition to the clock/time/timer message @@ -1902,12 +1902,14 @@ void IRac::vestel(IRVestelAc *ac, /// @param[in] turbo Run the device in turbo/powerful mode. /// @param[in] econo Run the device in economical mode. /// @param[in] light Turn on the LED/Display mode. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. void IRac::voltas(IRVoltas *ac, const voltas_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool econo, const bool light) { + const bool turbo, const bool econo, const bool light, + const int16_t sleep) { ac->begin(); ac->setModel(model); ac->setPower(on); @@ -1923,7 +1925,7 @@ void IRac::voltas(IRVoltas *ac, // No Filter setting available. // No Clean setting available. // No Beep setting available. - // No Sleep setting available. + ac->setSleep(sleep >= 0); // Sleep is either on/off, so convert to boolean. // No Clock setting available. ac->send(); } @@ -1940,7 +1942,7 @@ void IRac::voltas(IRVoltas *ac, /// @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] sleep Nr. of minutes for sleep mode. -1 is Off, > 0 is on. +/// @param[in] sleep Nr. of minutes for sleep mode. -1 is Off, >= 0 is on. /// @param[in] clock The time in Nr. of mins since midnight. < 0 is ignore. void IRac::whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model, const bool on, const stdAc::opmode_t mode, @@ -2500,7 +2502,7 @@ bool IRac::sendAc(const stdAc::state_t desired, const stdAc::state_t *prev) { IRVoltas ac(_pin, _inverted, _modulation); voltas(&ac, (voltas_ac_remote_model_t)send.model, send.power, send.mode, degC, send.fanspeed, send.swingv, send.swingh, send.turbo, - send.econo, send.light); + send.econo, send.light, send.sleep); break; } #endif // SEND_VOLTAS diff --git a/src/IRac.h b/src/IRac.h index d1e7247b4..9e210c83b 100644 --- a/src/IRac.h +++ b/src/IRac.h @@ -411,7 +411,8 @@ void electra(IRElectraAc *ac, const bool on, const stdAc::opmode_t mode, const float degrees, const stdAc::fanspeed_t fan, const stdAc::swingv_t swingv, const stdAc::swingh_t swingh, - const bool turbo, const bool econo, const bool light); + const bool turbo, const bool econo, const bool light, + const int16_t sleep = -1); #endif // SEND_VOLTAS #if SEND_WHIRLPOOL_AC void whirlpool(IRWhirlpoolAc *ac, const whirlpool_ac_remote_model_t model, diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 18ef6c809..d0e679f4b 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -382,6 +382,14 @@ void IRVoltas::setLight(const bool on) { _.Light = on; } /// @return true, the setting is on. false, the setting is off. bool IRVoltas::getLight(void) const { return _.Light; } +/// Change the Sleep setting. +/// @param[in] on true, the setting is on. false, the setting is off. +void IRVoltas::setSleep(const bool on) { _.Sleep = on; } + +/// Get the value of the current Sleep setting. +/// @return true, the setting is on. false, the setting is off. +bool IRVoltas::getSleep(void) const { return _.Sleep; } + /// Convert the current internal state into its stdAc::state_t equivilant. /// @param[in] prev Ptr to the previous state if available. /// @return The stdAc equivilant of the native settings. @@ -408,12 +416,12 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { result.turbo = _.Turbo; result.econo = _.Econo; result.light = _.Light; + result.sleep = _.Sleep ? 0 : -1; // Not supported. result.quiet = false; result.filter = false; result.clean = false; result.beep = false; - result.sleep = -1; result.clock = -1; return result; } @@ -422,7 +430,7 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { /// @return A human readable string. String IRVoltas::toString() { String result = ""; - result.reserve(120); // Reserve some heap for the string to reduce fragging. + result.reserve(140); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::VOLTAS, getModel(), false); result += addBoolToString(_.Power, kPowerStr); result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, @@ -439,5 +447,6 @@ String IRVoltas::toString() { result += addBoolToString(_.Econo, kEconoStr); result += addBoolToString(_.Wifi, kWifiStr); result += addBoolToString(_.Light, kLightStr); + result += addBoolToString(_.Sleep, kSleepStr); return result; } diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index dc198c04a..4349c9368 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -38,7 +38,7 @@ union VoltasProtocol { // Byte 2 uint8_t SwingV :3; uint8_t Wifi :1; - uint8_t :1; + uint8_t :1; // Unknown/Unused uint8_t Turbo :1; uint8_t Sleep :1; uint8_t Power :1; @@ -128,6 +128,8 @@ class IRVoltas { bool getLight(void) const; void setTurbo(const bool on); bool getTurbo(void) const; + void setSleep(const bool on); + bool getSleep(void) const; void setOffTimer(const uint16_t nr_of_mins); uint16_t getOffTimer(void); uint8_t* getRaw(void); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index d96afb316..d439910a2 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1565,7 +1565,7 @@ TEST(TestIRac, Voltas) { char expected_unknown[] = "Model: 0 (UNKNOWN), Power: On, Mode: 8 (Cool), Temp: 18C, " "Fan: 1 (High), Swing(V): On, Swing(H): On, " - "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; + "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On"; irac.voltas(&ac, voltas_ac_remote_model_t::kVoltasUnknown, // Model true, // Power @@ -1576,7 +1576,8 @@ TEST(TestIRac, Voltas) { stdAc::swingh_t::kAuto, // Horizontal Swing false, // Turbo false, // Econo - true); // Light + true, // Light + 3 * 60); // Sleep EXPECT_EQ(voltas_ac_remote_model_t::kVoltasUnknown, ac.getModel()); EXPECT_TRUE(ac.getPower()); EXPECT_EQ(kVoltasCool, ac.getMode()); @@ -1585,6 +1586,7 @@ TEST(TestIRac, Voltas) { EXPECT_FALSE(ac.getTurbo()); EXPECT_FALSE(ac.getEcono()); EXPECT_TRUE(ac.getLight()); + EXPECT_TRUE(ac.getSleep()); ASSERT_EQ(expected_unknown, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); @@ -1599,7 +1601,7 @@ TEST(TestIRac, Voltas) { char expected_122LZF[] = "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 18C, " "Fan: 1 (High), Swing(V): On, Swing(H): N/A, " - "Turbo: Off, Econo: Off, WiFi: Off, Light: On"; + "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On"; irac.voltas(&ac, voltas_ac_remote_model_t::kVoltas122LZF, // Model true, // Power @@ -1610,7 +1612,8 @@ TEST(TestIRac, Voltas) { stdAc::swingh_t::kAuto, // Horizontal Swing false, // Turbo false, // Econo - true); // Light + true, // Light + 3 * 60); // Sleep EXPECT_EQ(voltas_ac_remote_model_t::kVoltas122LZF, ac.getModel()); EXPECT_TRUE(ac.getPower()); EXPECT_EQ(kVoltasCool, ac.getMode()); @@ -1619,6 +1622,7 @@ TEST(TestIRac, Voltas) { EXPECT_FALSE(ac.getTurbo()); EXPECT_FALSE(ac.getEcono()); EXPECT_TRUE(ac.getLight()); + EXPECT_TRUE(ac.getSleep()); ASSERT_EQ(expected_122LZF, ac.toString()); ac._irsend.makeDecodeResult(); EXPECT_TRUE(capture.decode(&ac._irsend.capture)); diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 90bac39fe..eabc1c16a 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -42,7 +42,7 @@ TEST(TestDecodeVoltas, RealExample) { EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " "Swing(V): Off, Swing(H): N/A, " - "Turbo: Off, Econo: Off, WiFi: On, Light: Off", + "Turbo: Off, Econo: Off, WiFi: On, Light: Off, Sleep: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -140,6 +140,18 @@ TEST(TestIRVoltasClass, Turbo) { EXPECT_FALSE(ac.getTurbo()); } +TEST(TestIRVoltasClass, Sleep) { + IRVoltas ac(kGpioUnused); + ac.begin(); + + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); + ac.setSleep(true); + EXPECT_TRUE(ac.getSleep()); + ac.setSleep(false); + EXPECT_FALSE(ac.getSleep()); +} + TEST(TestIRVoltasClass, Econo) { IRVoltas ac(kGpioUnused); ac.begin(); From 5bf01d6a194a00b9e41543e68fb5bf8966371b55 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 13:42:07 +1000 Subject: [PATCH 09/16] Voltas: First attempt at On/Off timer support. * Add routines to support setting the on & off timer(s). * Unit tests updated. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-678825548 --- src/ir_Voltas.cpp | 68 +++++++++++++++++++++++++++++++++++++++-- src/ir_Voltas.h | 21 ++++++++----- test/IRac_test.cpp | 6 ++-- test/ir_Voltas_test.cpp | 59 ++++++++++++++++++++++++++++++++++- 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index d0e679f4b..c66d0de5f 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -93,9 +93,8 @@ IRVoltas::IRVoltas(const uint16_t pin, const bool inverted, // Reset the internal state to a fixed known good state. void IRVoltas::stateReset() { // This resets to a known-good state. - // ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-674699746 const uint8_t kReset[kVoltasStateLength] = { - 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xCB}; + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3A, 0x3A, 0x00, 0x00, 0xDE}; setRaw(kReset); } @@ -390,6 +389,65 @@ void IRVoltas::setSleep(const bool on) { _.Sleep = on; } /// @return true, the setting is on. false, the setting is off. bool IRVoltas::getSleep(void) const { return _.Sleep; } +/// Get the value of the On Timer time. +/// @note Rounded down to the hour mark. +/// @return Number of minutes before the timer activates. +uint16_t IRVoltas::getOnTime(void) const { + return (12 * _.OnTimer12Hr + _.OnTimerHrs) * 60; +} + +/// Is the On Timer enabled? +/// @return true, A timer is on. false, A timer is off. +bool IRVoltas::getOnTimerEnabled(void) const { + return _.TimerEnable_4 && _.TimerEnable_5 && getOnTime(); +} + +/// Set the value of the On Timer time. +/// @note Rounded down to the hour mark. Enables timer if >= 60 mins. +/// @param[in] nr_of_mins Number of minutes before the timer activates. +void IRVoltas::setOnTime(const uint16_t nr_of_mins) { + uint16_t hrs = std::min(nr_of_mins / 60, 23); // Cap to 23 hrs. + _.OnTimer12Hr = hrs >= 12; + _.OnTimerHrs = hrs % 12; + if (hrs) { // The timer is to be enabled. + _.TimerEnable_4 = true; + _.TimerEnable_5 = true; + } else if (!getOffTimerEnabled()) { // Can we disable the timer(s)? + _.TimerEnable_4 = false; + _.TimerEnable_5 = false; + } +} + +/// Get the value of the On Timer time. +/// @note Rounded down to the hour mark. +/// @return Number of minutes before the timer activates. +uint16_t IRVoltas::getOffTime(void) const { + return (12 * _.OffTimer12Hr + _.OffTimerHrs) * 60; +} + +/// Set the value of the Off Timer time. +/// @note Rounded down to the hour mark. Enables timer if >= 60 mins. +/// @param[in] nr_of_mins Number of minutes before the timer activates. +void IRVoltas::setOffTime(const uint16_t nr_of_mins) { + uint16_t hrs = std::min(nr_of_mins / 60, 23); // Cap to 23 hrs. + + _.OffTimer12Hr = hrs >= 12; + _.OffTimerHrs = hrs % 12; + if (hrs) { // The timer is to be enabled. + _.TimerEnable_4 = true; + _.TimerEnable_5 = true; + } else if (!getOnTimerEnabled()) { // Can we disable the timer(s)? + _.TimerEnable_4 = false; + _.TimerEnable_5 = false; + } +} + +/// Is the Off Timer enabled? +/// @return true, A timer is on. false, A timer is off. +bool IRVoltas::getOffTimerEnabled(void) const { + return _.TimerEnable_4 && _.TimerEnable_5 && getOffTime(); +} + /// Convert the current internal state into its stdAc::state_t equivilant. /// @param[in] prev Ptr to the previous state if available. /// @return The stdAc equivilant of the native settings. @@ -430,7 +488,7 @@ stdAc::state_t IRVoltas::toCommon(const stdAc::state_t *prev) { /// @return A human readable string. String IRVoltas::toString() { String result = ""; - result.reserve(140); // Reserve some heap for the string to reduce fragging. + result.reserve(200); // Reserve some heap for the string to reduce fragging. result += addModelToString(decode_type_t::VOLTAS, getModel(), false); result += addBoolToString(_.Power, kPowerStr); result += addModeToString(_.Mode, 255, kVoltasCool, kVoltasHeat, @@ -448,5 +506,9 @@ String IRVoltas::toString() { result += addBoolToString(_.Wifi, kWifiStr); result += addBoolToString(_.Light, kLightStr); result += addBoolToString(_.Sleep, kSleepStr); + result += addLabeledString(getOnTimerEnabled() ? minsToString(getOnTime()) + : kOffStr, kOnTimerStr); + result += addLabeledString(getOffTimerEnabled() ? minsToString(getOffTime()) + : kOffStr, kOffTimerStr); return result; } diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index 4349c9368..a9e1ec352 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -48,17 +48,18 @@ union VoltasProtocol { uint8_t Econo :1; uint8_t TempSet :1; // Byte 4 - uint8_t OffTimer24h4 :1; - uint8_t :7; // Typically 0b0011101 + uint8_t TimerEnable_4 :1; + uint8_t :6; // Typically 0b011101 + uint8_t OnTimer12Hr :1; // 1 if Timer is >= 12 hrs. // Byte 5 - uint8_t OffTimer24h5 :1; + uint8_t TimerEnable_5 :1; uint8_t :6; // Typically 0b011101 - uint8_t TimerAdd12Hr :1; + uint8_t OffTimer12Hr :1; // 1 if Timer is >= 12 hrs. // Byte 6 uint8_t :8; // Typically 0b00111011(0x3B) // Byte 7 - uint8_t :4; // Typically 0b0001 - uint8_t TimerHrs :4; // Nr of Hours. + uint8_t OnTimerHrs :4; // Nr of Hours. + uint8_t OffTimerHrs :4; // Nr of Hours. // Byte 8 uint8_t :5; // Typically 0b00000 uint8_t Light :1; @@ -130,8 +131,12 @@ class IRVoltas { bool getTurbo(void) const; void setSleep(const bool on); bool getSleep(void) const; - void setOffTimer(const uint16_t nr_of_mins); - uint16_t getOffTimer(void); + uint16_t getOnTime(void) const; + void setOnTime(const uint16_t nr_of_mins); + bool getOnTimerEnabled(void) const; + uint16_t getOffTime(void) const; + void setOffTime(const uint16_t nr_of_mins); + bool getOffTimerEnabled(void) const; uint8_t* getRaw(void); void setRaw(const uint8_t new_code[]); uint8_t convertMode(const stdAc::opmode_t mode); diff --git a/test/IRac_test.cpp b/test/IRac_test.cpp index d439910a2..c16af0ec9 100644 --- a/test/IRac_test.cpp +++ b/test/IRac_test.cpp @@ -1565,7 +1565,8 @@ TEST(TestIRac, Voltas) { char expected_unknown[] = "Model: 0 (UNKNOWN), Power: On, Mode: 8 (Cool), Temp: 18C, " "Fan: 1 (High), Swing(V): On, Swing(H): On, " - "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On"; + "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On, " + "On Timer: Off, Off Timer: Off"; irac.voltas(&ac, voltas_ac_remote_model_t::kVoltasUnknown, // Model true, // Power @@ -1601,7 +1602,8 @@ TEST(TestIRac, Voltas) { char expected_122LZF[] = "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 18C, " "Fan: 1 (High), Swing(V): On, Swing(H): N/A, " - "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On"; + "Turbo: Off, Econo: Off, WiFi: Off, Light: On, Sleep: On, " + "On Timer: Off, Off Timer: Off"; irac.voltas(&ac, voltas_ac_remote_model_t::kVoltas122LZF, // Model true, // Power diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index eabc1c16a..ffd1da0b3 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -42,7 +42,8 @@ TEST(TestDecodeVoltas, RealExample) { EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " "Swing(V): Off, Swing(H): N/A, " - "Turbo: Off, Econo: Off, WiFi: On, Light: Off, Sleep: Off", + "Turbo: Off, Econo: Off, WiFi: On, Light: Off, Sleep: Off, " + "On Timer: 01:00, Off Timer: 01:00", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); @@ -84,6 +85,10 @@ TEST(TestIRVoltasClass, Checksums) { 0x33, 0x84, 0x88, 0x18, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0x00}; EXPECT_FALSE(IRVoltas::validChecksum(badchecksum)); EXPECT_EQ(0xE6, IRVoltas::calcChecksum(badchecksum)); + const uint8_t kReset[kVoltasStateLength] = { + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3A, 0x3A, 0x00, 0x00, 0xDE}; + EXPECT_TRUE(IRVoltas::validChecksum(kReset)); + EXPECT_EQ(0xDE, IRVoltas::calcChecksum(kReset)); } TEST(TestIRVoltasClass, SetandGetRaw) { @@ -316,3 +321,55 @@ TEST(TestVoltasClass, SwingH) { EXPECT_FALSE(ac.getSwingHChange()); EXPECT_FALSE(ac.getSwingH()); } + +TEST(TestVoltasClass, HumanReadable) { + IRVoltas ac(kGpioUnused); + EXPECT_EQ( + "Model: 1 (122LZF), Power: Off, Mode: 8 (Cool), Temp: 23C, " + "Fan: 1 (High), Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, " + "WiFi: Off, Light: Off, Sleep: Off, On Timer: Off, Off Timer: Off", + ac.toString()); + ac.on(); + ac.setMode(kVoltasHeat); + ac.setTemp(21); + ac.setFan(kVoltasFanAuto); + ac.setSwingV(true); + ac.setTurbo(true); + ac.setWifi(true); + ac.setLight(true); + ac.setSleep(true); + ac.setOnTime(2 * 60); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 2 (Heat), Temp: 21C, " + "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: Off, " + "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: Off", + ac.toString()); + ac.setOffTime(13 * 60); + ac.setMode(kVoltasCool); + ac.setEcono(true); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " + "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: On, " + "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: 13:00", + ac.toString()); + ac.setModel(voltas_ac_remote_model_t::kVoltasUnknown); + ac.setSwingH(true); + EXPECT_EQ( + "Model: 0 (UNKNOWN), Power: On, Mode: 8 (Cool), Temp: 21C, " + "Fan: 7 (Auto), Swing(V): On, Swing(H): On, Turbo: On, Econo: On, " + "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: 13:00", + ac.toString()); + ac.setModel(voltas_ac_remote_model_t::kVoltas122LZF); + ac.setOnTime(0); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " + "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: On, " + "WiFi: On, Light: On, Sleep: On, On Timer: Off, Off Timer: 13:00", + ac.toString()); + ac.setOffTime(0); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " + "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: On, " + "WiFi: On, Light: On, Sleep: On, On Timer: Off, Off Timer: Off", + ac.toString()); +} From c0f958816609f6f22bf17eeb6ffc12a088e020c9 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 14:08:08 +1000 Subject: [PATCH 10/16] Voltas: Limit fan speed to Hi, Med, & Lo in Fan mode. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679549395 --- src/ir_Voltas.cpp | 11 ++++++++--- test/ir_Voltas_test.cpp | 13 +++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index c66d0de5f..bc12396a7 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -194,9 +194,10 @@ bool IRVoltas::getPower(void) const { return _.Power; } /// @param[in] mode The desired operating mode. /// @note If we get an unexpected mode, default to AUTO. void IRVoltas::setMode(const uint8_t mode) { + _.Mode = mode; switch (mode) { case kVoltasFan: - setFan(kVoltasFanHigh); + setFan(getFan()); // Force the fan speed to a correct one fo the mode. break; case kVoltasDry: setFan(kVoltasFanLow); @@ -209,7 +210,6 @@ void IRVoltas::setMode(const uint8_t mode) { setMode(kVoltasCool); return; } - _.Mode = mode; setEcono(getEcono()); // Reset the econo setting if needed. } @@ -257,10 +257,15 @@ uint8_t IRVoltas::getTemp(void) { return _.Temp + kVoltasMinTemp; } /// @param[in] fan The desired setting. void IRVoltas::setFan(const uint8_t fan) { switch (fan) { + case kVoltasFanAuto: + if (_.Mode == kVoltasFan) { // Auto speed is not available in fan mode. + setFan(kVoltasFanHigh); + return; + } + // FALL-THRU case kVoltasFanLow: case kVoltasFanMed: case kVoltasFanHigh: - case kVoltasFanAuto: _.FanSpeed = fan; break; default: diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index ffd1da0b3..0e15a88a4 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -255,6 +255,7 @@ TEST(TestVoltasClass, Temperature) { TEST(TestVoltasClass, FanSpeed) { IRVoltas ac(kGpioUnused); ac.begin(); + ac.setMode(kVoltasCool); // All fan speeds are allowed in cool mode. ac.setFan(kVoltasFanLow); ac.setFan(kVoltasFanAuto); @@ -272,6 +273,18 @@ TEST(TestVoltasClass, FanSpeed) { ac.setFan(255); EXPECT_EQ(kVoltasFanAuto, ac.getFan()); + + // Confirm auto speed isn't operable in Fan mode. + ac.setMode(kVoltasFan); + EXPECT_NE(kVoltasFanAuto, ac.getFan()); + ac.setFan(kVoltasFanLow); + EXPECT_EQ(kVoltasFanLow, ac.getFan()); + ac.setFan(kVoltasFanMed); + EXPECT_EQ(kVoltasFanMed, ac.getFan()); + ac.setFan(kVoltasFanHigh); + EXPECT_EQ(kVoltasFanHigh, ac.getFan()); + ac.setFan(kVoltasFanAuto); + EXPECT_NE(kVoltasFanAuto, ac.getFan()); } TEST(TestVoltasClass, SwingV) { From e363adc3c96107cacc26c975775f357c6c64d0be Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 14:20:23 +1000 Subject: [PATCH 11/16] Voltas: Improve timer support. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679553561 --- src/ir_Voltas.cpp | 24 ++++++++++++++++-------- src/ir_Voltas.h | 2 +- test/ir_Voltas_test.cpp | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index bc12396a7..585c33193 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -404,7 +404,7 @@ uint16_t IRVoltas::getOnTime(void) const { /// Is the On Timer enabled? /// @return true, A timer is on. false, A timer is off. bool IRVoltas::getOnTimerEnabled(void) const { - return _.TimerEnable_4 && _.TimerEnable_5 && getOnTime(); + return _.TimerEnable_4 && _.TimerEnable_5 && _.OnTimerEnable && getOnTime(); } /// Set the value of the On Timer time. @@ -417,9 +417,13 @@ void IRVoltas::setOnTime(const uint16_t nr_of_mins) { if (hrs) { // The timer is to be enabled. _.TimerEnable_4 = true; _.TimerEnable_5 = true; - } else if (!getOffTimerEnabled()) { // Can we disable the timer(s)? - _.TimerEnable_4 = false; - _.TimerEnable_5 = false; + _.OnTimerEnable = true; + } else { + _.OnTimerEnable = false; + if (!getOffTimerEnabled()) { // Can we disable the timer(s)? + _.TimerEnable_4 = false; + _.TimerEnable_5 = false; + } } } @@ -441,16 +445,20 @@ void IRVoltas::setOffTime(const uint16_t nr_of_mins) { if (hrs) { // The timer is to be enabled. _.TimerEnable_4 = true; _.TimerEnable_5 = true; - } else if (!getOnTimerEnabled()) { // Can we disable the timer(s)? - _.TimerEnable_4 = false; - _.TimerEnable_5 = false; + _.OffTimerEnable = true; + } else { + _.OffTimerEnable = false; + if (!getOnTimerEnabled()) { // Can we disable the timer(s)? + _.TimerEnable_4 = false; + _.TimerEnable_5 = false; + } } } /// Is the Off Timer enabled? /// @return true, A timer is on. false, A timer is off. bool IRVoltas::getOffTimerEnabled(void) const { - return _.TimerEnable_4 && _.TimerEnable_5 && getOffTime(); + return _.TimerEnable_4 && _.TimerEnable_5 && _.OffTimerEnable && getOffTime(); } /// Convert the current internal state into its stdAc::state_t equivilant. diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index a9e1ec352..f2bb1e48e 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -64,7 +64,7 @@ union VoltasProtocol { uint8_t :5; // Typically 0b00000 uint8_t Light :1; uint8_t OffTimerEnable :1; - uint8_t :1; // Typically 0b0 + uint8_t OnTimerEnable :1; // Byte 9 uint8_t Checksum :8; }; diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 0e15a88a4..eec8a4729 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -43,7 +43,7 @@ TEST(TestDecodeVoltas, RealExample) { "Model: 1 (122LZF), Power: On, Mode: 4 (Dry), Temp: 24C, Fan: 4 (Low), " "Swing(V): Off, Swing(H): N/A, " "Turbo: Off, Econo: Off, WiFi: On, Light: Off, Sleep: Off, " - "On Timer: 01:00, Off Timer: 01:00", + "On Timer: Off, Off Timer: Off", IRAcUtils::resultAcToString(&irsend.capture)); stdAc::state_t r, p; ASSERT_TRUE(IRAcUtils::decodeToState(&irsend.capture, &r, &p)); From 797dec5a3d0e7952cd27cdc9c7e0c24aebeb0052 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 14:29:56 +1000 Subject: [PATCH 12/16] Voltas: Update decode/send status to STABLE. For #1238 --- src/ir_Voltas.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 585c33193..ab7ef0e05 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -28,7 +28,7 @@ const uint16_t kVoltasFreq = 38000; ///< Hz. #if SEND_VOLTAS /// Send a Voltas formatted message. -/// Status: ALPHA / Untested. +/// Status: STABLE / Working on real device. /// @param[in] data An array of bytes containing the IR command. /// It is assumed to be in MSB order for this code. /// e.g. @@ -51,7 +51,7 @@ void IRsend::sendVoltas(const uint8_t data[], const uint16_t nbytes, #if DECODE_VOLTAS /// Decode the supplied Voltas message. -/// Status: ALPHA / Untested. +/// Status: STABLE / Working on real device. /// @param[in,out] results Ptr to the data to decode & where to store the decode /// @param[in] offset The starting index to use when attempting to decode the /// raw data. Typically/Defaults to kStartOffset. From 8c5f27ea25019ed97df3558783742df3a49f29c2 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 18:55:20 +1000 Subject: [PATCH 13/16] Voltas: Turbo & Sleep settings only available in Cool mode. * Implement these restrictions. * Add/update unit tests accordingly. Ref #1238 --- src/ir_Voltas.cpp | 25 ++++++++++++++++++++----- test/ir_Voltas_test.cpp | 11 +++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index ab7ef0e05..bbf72175b 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -210,7 +210,10 @@ void IRVoltas::setMode(const uint8_t mode) { setMode(kVoltasCool); return; } - setEcono(getEcono()); // Reset the econo setting if needed. + // Reset some settings if needed. + setEcono(getEcono()); + setTurbo(getTurbo()); + setSleep(getSleep()); } /// Get the operating mode setting of the A/C. @@ -358,16 +361,22 @@ bool IRVoltas::getWifi(void) const { return _.Wifi; } /// Change the Turbo setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRVoltas::setTurbo(const bool on) { _.Turbo = on; } +/// @note The Turbo setting is only available in Cool mode. +void IRVoltas::setTurbo(const bool on) { + if (on && _.Mode == kVoltasCool) + _.Turbo = true; + else + _.Turbo = false; +} /// Get the value of the current Turbo setting. /// @return true, the setting is on. false, the setting is off. bool IRVoltas::getTurbo(void) const { return _.Turbo; } -/// Change the Econo setting. +/// Change the Economy setting. /// @param[in] on true, the setting is on. false, the setting is off. +/// @note The Economy setting is only available in Cool mode. void IRVoltas::setEcono(const bool on) { - // The econo setting is only available in cool mode. if (on && _.Mode == kVoltasCool) _.Econo = true; else @@ -388,7 +397,13 @@ bool IRVoltas::getLight(void) const { return _.Light; } /// Change the Sleep setting. /// @param[in] on true, the setting is on. false, the setting is off. -void IRVoltas::setSleep(const bool on) { _.Sleep = on; } +/// @note The Sleep setting is only available in Cool mode. +void IRVoltas::setSleep(const bool on) { + if (on && _.Mode == kVoltasCool) + _.Sleep = true; + else + _.Sleep = false; +} /// Get the value of the current Sleep setting. /// @return true, the setting is on. false, the setting is off. diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index eec8a4729..fb3e44cc0 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -343,22 +343,25 @@ TEST(TestVoltasClass, HumanReadable) { "WiFi: Off, Light: Off, Sleep: Off, On Timer: Off, Off Timer: Off", ac.toString()); ac.on(); - ac.setMode(kVoltasHeat); ac.setTemp(21); ac.setFan(kVoltasFanAuto); ac.setSwingV(true); - ac.setTurbo(true); ac.setWifi(true); ac.setLight(true); + ac.setTurbo(true); ac.setSleep(true); + ac.setEcono(true); ac.setOnTime(2 * 60); + ac.setMode(kVoltasHeat); // Heat mode should cancel Sleep, Turbo, & Econo. EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 2 (Heat), Temp: 21C, " - "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: Off, " - "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: Off", + "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: Off, Econo: Off, " + "WiFi: On, Light: On, Sleep: Off, On Timer: 02:00, Off Timer: Off", ac.toString()); ac.setOffTime(13 * 60); ac.setMode(kVoltasCool); + ac.setTurbo(true); + ac.setSleep(true); ac.setEcono(true); EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " From de3f535ad0d61807645d1cbcfdabf6b02d3fb459 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 20:19:34 +1000 Subject: [PATCH 14/16] Voltas: Increase Timer resolution to include minutes. * Remove incorrect assumptions about timer enable flags. e.g. `TimerEnable_4` etc. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679924827 --- src/ir_Voltas.cpp | 49 ++++++++++++++--------------------------- src/ir_Voltas.h | 12 +++++----- test/ir_Voltas_test.cpp | 12 +++++----- 3 files changed, 28 insertions(+), 45 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index bbf72175b..24b419c13 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -410,70 +410,53 @@ void IRVoltas::setSleep(const bool on) { bool IRVoltas::getSleep(void) const { return _.Sleep; } /// Get the value of the On Timer time. -/// @note Rounded down to the hour mark. /// @return Number of minutes before the timer activates. uint16_t IRVoltas::getOnTime(void) const { - return (12 * _.OnTimer12Hr + _.OnTimerHrs) * 60; + return (12 * _.OnTimer12Hr + _.OnTimerHrs) * 60 + _.OnTimerMins; } /// Is the On Timer enabled? /// @return true, A timer is on. false, A timer is off. bool IRVoltas::getOnTimerEnabled(void) const { - return _.TimerEnable_4 && _.TimerEnable_5 && _.OnTimerEnable && getOnTime(); + return _.OnTimerEnable && getOnTime(); } /// Set the value of the On Timer time. -/// @note Rounded down to the hour mark. Enables timer if >= 60 mins. /// @param[in] nr_of_mins Number of minutes before the timer activates. +/// 0 disables the timer. Max is 23 hrs & 59 mins (1439 mins) void IRVoltas::setOnTime(const uint16_t nr_of_mins) { - uint16_t hrs = std::min(nr_of_mins / 60, 23); // Cap to 23 hrs. + // Cap the total number of mins. + uint16_t mins = std::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); + uint16_t hrs = mins / 60; + _.OnTimerMins = mins % 60; _.OnTimer12Hr = hrs >= 12; _.OnTimerHrs = hrs % 12; - if (hrs) { // The timer is to be enabled. - _.TimerEnable_4 = true; - _.TimerEnable_5 = true; - _.OnTimerEnable = true; - } else { - _.OnTimerEnable = false; - if (!getOffTimerEnabled()) { // Can we disable the timer(s)? - _.TimerEnable_4 = false; - _.TimerEnable_5 = false; - } - } + _.OnTimerEnable = (mins > 0); // Is the timer is to be enabled? } /// Get the value of the On Timer time. -/// @note Rounded down to the hour mark. /// @return Number of minutes before the timer activates. uint16_t IRVoltas::getOffTime(void) const { - return (12 * _.OffTimer12Hr + _.OffTimerHrs) * 60; + return (12 * _.OffTimer12Hr + _.OffTimerHrs) * 60 + _.OffTimerMins; } /// Set the value of the Off Timer time. -/// @note Rounded down to the hour mark. Enables timer if >= 60 mins. /// @param[in] nr_of_mins Number of minutes before the timer activates. +/// 0 disables the timer. Max is 23 hrs & 59 mins (1439 mins) void IRVoltas::setOffTime(const uint16_t nr_of_mins) { - uint16_t hrs = std::min(nr_of_mins / 60, 23); // Cap to 23 hrs. - + // Cap the total number of mins. + uint16_t mins = std::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); + uint16_t hrs = mins / 60; + _.OffTimerMins = mins % 60; _.OffTimer12Hr = hrs >= 12; _.OffTimerHrs = hrs % 12; - if (hrs) { // The timer is to be enabled. - _.TimerEnable_4 = true; - _.TimerEnable_5 = true; - _.OffTimerEnable = true; - } else { - _.OffTimerEnable = false; - if (!getOnTimerEnabled()) { // Can we disable the timer(s)? - _.TimerEnable_4 = false; - _.TimerEnable_5 = false; - } - } + _.OffTimerEnable = (mins > 0); // Is the timer is to be enabled? } /// Is the Off Timer enabled? /// @return true, A timer is on. false, A timer is off. bool IRVoltas::getOffTimerEnabled(void) const { - return _.TimerEnable_4 && _.TimerEnable_5 && _.OffTimerEnable && getOffTime(); + return _.OffTimerEnable && getOffTime(); } /// Convert the current internal state into its stdAc::state_t equivilant. diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index f2bb1e48e..746f7b8e9 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -33,7 +33,7 @@ union VoltasProtocol { uint8_t SwingHChange :7; // Byte 1 uint8_t Mode :4; - uint8_t :1; + uint8_t :1; // Unknown/Unused uint8_t FanSpeed :3; // Byte 2 uint8_t SwingV :3; @@ -44,16 +44,16 @@ union VoltasProtocol { uint8_t Power :1; // Byte 3 uint8_t Temp :4; - uint8_t Unknown3 :2; // Typically 0b01 + uint8_t :2; // Typically 0b01 uint8_t Econo :1; uint8_t TempSet :1; // Byte 4 - uint8_t TimerEnable_4 :1; - uint8_t :6; // Typically 0b011101 + uint8_t OnTimerMins :6; // 0-59 + uint8_t :1; // Unknown/Unused uint8_t OnTimer12Hr :1; // 1 if Timer is >= 12 hrs. // Byte 5 - uint8_t TimerEnable_5 :1; - uint8_t :6; // Typically 0b011101 + uint8_t OffTimerMins :6; // 0-59 + uint8_t :1; // Unknown/Unused uint8_t OffTimer12Hr :1; // 1 if Timer is >= 12 hrs. // Byte 6 uint8_t :8; // Typically 0b00111011(0x3B) diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index fb3e44cc0..5b1932679 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -351,14 +351,14 @@ TEST(TestVoltasClass, HumanReadable) { ac.setTurbo(true); ac.setSleep(true); ac.setEcono(true); - ac.setOnTime(2 * 60); + ac.setOnTime(2 * 60 + 17); ac.setMode(kVoltasHeat); // Heat mode should cancel Sleep, Turbo, & Econo. EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 2 (Heat), Temp: 21C, " "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: Off, Econo: Off, " - "WiFi: On, Light: On, Sleep: Off, On Timer: 02:00, Off Timer: Off", + "WiFi: On, Light: On, Sleep: Off, On Timer: 02:17, Off Timer: Off", ac.toString()); - ac.setOffTime(13 * 60); + ac.setOffTime(13 * 60 + 37); ac.setMode(kVoltasCool); ac.setTurbo(true); ac.setSleep(true); @@ -366,21 +366,21 @@ TEST(TestVoltasClass, HumanReadable) { EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: On, " - "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: 13:00", + "WiFi: On, Light: On, Sleep: On, On Timer: 02:17, Off Timer: 13:37", ac.toString()); ac.setModel(voltas_ac_remote_model_t::kVoltasUnknown); ac.setSwingH(true); EXPECT_EQ( "Model: 0 (UNKNOWN), Power: On, Mode: 8 (Cool), Temp: 21C, " "Fan: 7 (Auto), Swing(V): On, Swing(H): On, Turbo: On, Econo: On, " - "WiFi: On, Light: On, Sleep: On, On Timer: 02:00, Off Timer: 13:00", + "WiFi: On, Light: On, Sleep: On, On Timer: 02:17, Off Timer: 13:37", ac.toString()); ac.setModel(voltas_ac_remote_model_t::kVoltas122LZF); ac.setOnTime(0); EXPECT_EQ( "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 21C, " "Fan: 7 (Auto), Swing(V): On, Swing(H): N/A, Turbo: On, Econo: On, " - "WiFi: On, Light: On, Sleep: On, On Timer: Off, Off Timer: 13:00", + "WiFi: On, Light: On, Sleep: On, On Timer: Off, Off Timer: 13:37", ac.toString()); ac.setOffTime(0); EXPECT_EQ( From 60193d3ca3d594fc1ff6b3798b1da5622bfb6ba5 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 22:15:43 +1000 Subject: [PATCH 15/16] Voltas: Timer improvements. * Remove `getOn/OffTimerEnabled()` * Fix Timer hour value off-by-one issue. * Unit test cases (inc real data) for timers. * Revert `kReset` to original value. * Update comments. For #1238 --- src/ir_Voltas.cpp | 37 ++++++++++++++----------------------- src/ir_Voltas.h | 10 ++++------ test/ir_Voltas_test.cpp | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/ir_Voltas.cpp b/src/ir_Voltas.cpp index 24b419c13..fe3e507cb 100644 --- a/src/ir_Voltas.cpp +++ b/src/ir_Voltas.cpp @@ -93,8 +93,9 @@ IRVoltas::IRVoltas(const uint16_t pin, const bool inverted, // Reset the internal state to a fixed known good state. void IRVoltas::stateReset() { // This resets to a known-good state. + // ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-674699746 const uint8_t kReset[kVoltasStateLength] = { - 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3A, 0x3A, 0x00, 0x00, 0xDE}; + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xCB}; setRaw(kReset); } @@ -412,13 +413,8 @@ bool IRVoltas::getSleep(void) const { return _.Sleep; } /// Get the value of the On Timer time. /// @return Number of minutes before the timer activates. uint16_t IRVoltas::getOnTime(void) const { - return (12 * _.OnTimer12Hr + _.OnTimerHrs) * 60 + _.OnTimerMins; -} - -/// Is the On Timer enabled? -/// @return true, A timer is on. false, A timer is off. -bool IRVoltas::getOnTimerEnabled(void) const { - return _.OnTimerEnable && getOnTime(); + return std::min((unsigned)(12 * _.OnTimer12Hr + _.OnTimerHrs - 1), 23U) * 60 + + _.OnTimerMins; } /// Set the value of the On Timer time. @@ -427,9 +423,9 @@ bool IRVoltas::getOnTimerEnabled(void) const { void IRVoltas::setOnTime(const uint16_t nr_of_mins) { // Cap the total number of mins. uint16_t mins = std::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); - uint16_t hrs = mins / 60; + uint16_t hrs = (mins / 60) + 1; _.OnTimerMins = mins % 60; - _.OnTimer12Hr = hrs >= 12; + _.OnTimer12Hr = hrs / 12; _.OnTimerHrs = hrs % 12; _.OnTimerEnable = (mins > 0); // Is the timer is to be enabled? } @@ -437,7 +433,8 @@ void IRVoltas::setOnTime(const uint16_t nr_of_mins) { /// Get the value of the On Timer time. /// @return Number of minutes before the timer activates. uint16_t IRVoltas::getOffTime(void) const { - return (12 * _.OffTimer12Hr + _.OffTimerHrs) * 60 + _.OffTimerMins; + return std::min((unsigned)(12 * _.OffTimer12Hr + _.OffTimerHrs - 1), 23U) * + 60 + _.OffTimerMins; } /// Set the value of the Off Timer time. @@ -446,19 +443,13 @@ uint16_t IRVoltas::getOffTime(void) const { void IRVoltas::setOffTime(const uint16_t nr_of_mins) { // Cap the total number of mins. uint16_t mins = std::min(nr_of_mins, (uint16_t)(23 * 60 + 59)); - uint16_t hrs = mins / 60; + uint16_t hrs = (mins / 60) + 1; _.OffTimerMins = mins % 60; - _.OffTimer12Hr = hrs >= 12; + _.OffTimer12Hr = hrs / 12; _.OffTimerHrs = hrs % 12; _.OffTimerEnable = (mins > 0); // Is the timer is to be enabled? } -/// Is the Off Timer enabled? -/// @return true, A timer is on. false, A timer is off. -bool IRVoltas::getOffTimerEnabled(void) const { - return _.OffTimerEnable && getOffTime(); -} - /// Convert the current internal state into its stdAc::state_t equivilant. /// @param[in] prev Ptr to the previous state if available. /// @return The stdAc equivilant of the native settings. @@ -517,9 +508,9 @@ String IRVoltas::toString() { result += addBoolToString(_.Wifi, kWifiStr); result += addBoolToString(_.Light, kLightStr); result += addBoolToString(_.Sleep, kSleepStr); - result += addLabeledString(getOnTimerEnabled() ? minsToString(getOnTime()) - : kOffStr, kOnTimerStr); - result += addLabeledString(getOffTimerEnabled() ? minsToString(getOffTime()) - : kOffStr, kOffTimerStr); + result += addLabeledString(_.OnTimerEnable ? minsToString(getOnTime()) + : kOffStr, kOnTimerStr); + result += addLabeledString(_.OffTimerEnable ? minsToString(getOffTime()) + : kOffStr, kOffTimerStr); return result; } diff --git a/src/ir_Voltas.h b/src/ir_Voltas.h index 746f7b8e9..b880e5e45 100644 --- a/src/ir_Voltas.h +++ b/src/ir_Voltas.h @@ -50,16 +50,16 @@ union VoltasProtocol { // Byte 4 uint8_t OnTimerMins :6; // 0-59 uint8_t :1; // Unknown/Unused - uint8_t OnTimer12Hr :1; // 1 if Timer is >= 12 hrs. + uint8_t OnTimer12Hr :1; // (Nr of Hours + 1) % 12. // Byte 5 uint8_t OffTimerMins :6; // 0-59 uint8_t :1; // Unknown/Unused - uint8_t OffTimer12Hr :1; // 1 if Timer is >= 12 hrs. + uint8_t OffTimer12Hr :1; // (Nr of Hours + 1) % 12. // Byte 6 uint8_t :8; // Typically 0b00111011(0x3B) // Byte 7 - uint8_t OnTimerHrs :4; // Nr of Hours. - uint8_t OffTimerHrs :4; // Nr of Hours. + uint8_t OnTimerHrs :4; // (Nr of Hours + 1) % 12. + uint8_t OffTimerHrs :4; // (Nr of Hours + 1) % 12. // Byte 8 uint8_t :5; // Typically 0b00000 uint8_t Light :1; @@ -133,10 +133,8 @@ class IRVoltas { bool getSleep(void) const; uint16_t getOnTime(void) const; void setOnTime(const uint16_t nr_of_mins); - bool getOnTimerEnabled(void) const; uint16_t getOffTime(void) const; void setOffTime(const uint16_t nr_of_mins); - bool getOffTimerEnabled(void) const; uint8_t* getRaw(void); void setRaw(const uint8_t new_code[]); uint8_t convertMode(const stdAc::opmode_t mode); diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index 5b1932679..d499437a7 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -86,9 +86,9 @@ TEST(TestIRVoltasClass, Checksums) { EXPECT_FALSE(IRVoltas::validChecksum(badchecksum)); EXPECT_EQ(0xE6, IRVoltas::calcChecksum(badchecksum)); const uint8_t kReset[kVoltasStateLength] = { - 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3A, 0x3A, 0x00, 0x00, 0xDE}; + 0x33, 0x28, 0x00, 0x17, 0x3B, 0x3B, 0x3B, 0x11, 0x00, 0xCB}; EXPECT_TRUE(IRVoltas::validChecksum(kReset)); - EXPECT_EQ(0xDE, IRVoltas::calcChecksum(kReset)); + EXPECT_EQ(0xCB, IRVoltas::calcChecksum(kReset)); } TEST(TestIRVoltasClass, SetandGetRaw) { @@ -389,3 +389,32 @@ TEST(TestVoltasClass, HumanReadable) { "WiFi: On, Light: On, Sleep: On, On Timer: Off, Off Timer: Off", ac.toString()); } + +TEST(TestVoltasClass, Timers) { + IRVoltas ac(kGpioUnused); + const uint8_t off_7hrs[10] = { + 0x33, 0x28, 0x80, 0x1B, 0x3B, 0x3B, 0x3B, 0x71, 0x40, 0xA7}; + ac.setRaw(off_7hrs); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 27C, Fan: 1 (High), " + "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " + "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 06:59", + ac.toString()); + const uint8_t off_16hrs[10] = { + 0x33, 0x28, 0x80, 0x1B, 0x3B, 0xBB, 0x3B, 0x41, 0x40, 0x57}; + ac.setRaw(off_16hrs); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 27C, Fan: 1 (High), " + "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " + "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 15:59", + ac.toString()); + ac.setOffTime(23 * 60 + 59); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 27C, Fan: 1 (High), " + "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " + "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 23:59", + ac.toString()); + const uint8_t off_24hrs[10] = { // Guess only + 0x33, 0x28, 0x80, 0x1B, 0x3B, 0x3B, 0x3B, 0x01, 0x40, 0x17}; + EXPECT_STATE_EQ(off_24hrs, ac.getRaw(), kVoltasBits); +} From 0229e74952e05457b2b0d4a13ba891c256b5c7c8 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 25 Aug 2020 22:48:47 +1000 Subject: [PATCH 16/16] Voltas: Update Timer unit tests with real data. Ref: https://github.com/crankyoldgit/IRremoteESP8266/issues/1238#issuecomment-679999187 --- test/ir_Voltas_test.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/ir_Voltas_test.cpp b/test/ir_Voltas_test.cpp index d499437a7..38fa9223b 100644 --- a/test/ir_Voltas_test.cpp +++ b/test/ir_Voltas_test.cpp @@ -392,7 +392,7 @@ TEST(TestVoltasClass, HumanReadable) { TEST(TestVoltasClass, Timers) { IRVoltas ac(kGpioUnused); - const uint8_t off_7hrs[10] = { + const uint8_t off_7hrs[10] = { // Real Data 0x33, 0x28, 0x80, 0x1B, 0x3B, 0x3B, 0x3B, 0x71, 0x40, 0xA7}; ac.setRaw(off_7hrs); EXPECT_EQ( @@ -400,7 +400,7 @@ TEST(TestVoltasClass, Timers) { "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 06:59", ac.toString()); - const uint8_t off_16hrs[10] = { + const uint8_t off_16hrs[10] = { // Real Data 0x33, 0x28, 0x80, 0x1B, 0x3B, 0xBB, 0x3B, 0x41, 0x40, 0x57}; ac.setRaw(off_16hrs); EXPECT_EQ( @@ -414,7 +414,12 @@ TEST(TestVoltasClass, Timers) { "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 23:59", ac.toString()); - const uint8_t off_24hrs[10] = { // Guess only - 0x33, 0x28, 0x80, 0x1B, 0x3B, 0x3B, 0x3B, 0x01, 0x40, 0x17}; - EXPECT_STATE_EQ(off_24hrs, ac.getRaw(), kVoltasBits); + const uint8_t off_24hrs[10] = { // Real Data + 0x33, 0x28, 0x80, 0x1B, 0x3A, 0x3A, 0x3B, 0x01, 0x40, 0x19}; + ac.setRaw(off_24hrs); + EXPECT_EQ( + "Model: 1 (122LZF), Power: On, Mode: 8 (Cool), Temp: 27C, Fan: 1 (High), " + "Swing(V): Off, Swing(H): N/A, Turbo: Off, Econo: Off, WiFi: Off, " + "Light: Off, Sleep: Off, On Timer: Off, Off Timer: 23:58", + ac.toString()); }